<commit_before>// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

couchTests.design_docs = function(debug) {
  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
  var db2 = new CouchDB("test_suite_db_a", {"X-Couch-Full-Commit":"false"});
  db.deleteDb();
  db.createDb();
  db2.deleteDb();
  db2.createDb();
  if (debug) debugger;

  run_on_modified_server(
    [{section: "query_server_config",
      key: "reduce_limit",
      value: "false"}],
function() {

  var numDocs = 500;

  function makebigstring(power) {
    var str = "a";
    while(power-- > 0) {
      str = str + str;
    }
    return str;
  }

  var designDoc = {
    _id:"_design/test", // turn off couch.js id escaping?
    language: "javascript",
    whatever : {
      stringzone : "exports.string = 'plankton';",
      commonjs : {
        whynot : "exports.test = require('../stringzone'); exports.foo = require('whatever/stringzone');",
        upper : "exports.testing = require('./whynot').test.string.toUpperCase()+module.id+require('./whynot').foo.string"
      }
    },
    views: {
      all_docs_twice: {map: "function(doc) { emit(doc.integer, null); emit(doc.integer, null) }"},
      no_docs: {map: "function(doc) {}"},
      single_doc: {map: "function(doc) { if (doc._id == \"1\") { emit(1, null) }}"},
      summate: {map:"function (doc) {emit(doc.integer, doc.integer)};",
                reduce:"function (keys, values) { return sum(values); };"},
      summate2: {map:"function (doc) {emit(doc.integer, doc.integer)};",
                reduce:"function (keys, values) { return sum(values); };"},
      huge_src_and_results: {map: "function(doc) { if (doc._id == \"1\") { emit(\"" + makebigstring(16) + "\", null) }}",
                reduce:"function (keys, values) { return \"" + makebigstring(16) + "\"; };"}
    },
    shows: {
      simple: "function() {return 'ok'};",
      requirey : "function() { var lib = require('whatever/commonjs/upper'); return lib.testing; };",
      circular : "function() { var lib = require('whatever/commonjs/upper'); return JSON.stringify(this); };"
    }
  };

  var xhr = CouchDB.request("PUT", "/test_suite_db_a/_design/test", {body: JSON.stringify(designDoc)});
  var resp = JSON.parse(xhr.responseText);

  TEquals(resp.rev, db.save(designDoc).rev);

  // test that editing a show fun on the ddoc results in a change in output
  var xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/simple");
  T(xhr.status == 200);
  TEquals(xhr.responseText, "ok");

  designDoc.shows.simple = "function() {return 'ko'};"
  T(db.save(designDoc).ok);

  var xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/simple");
  T(xhr.status == 200);
  TEquals(xhr.responseText, "ko");

  var xhr = CouchDB.request("GET", "/test_suite_db_a/_design/test/_show/simple?cache=buster");
  T(xhr.status == 200);
  TEquals("ok", xhr.responseText, 'query server used wrong ddoc');

  // test commonjs require
  var xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/requirey");
  T(xhr.status == 200);
  TEquals("PLANKTONwhatever/commonjs/upperplankton", xhr.responseText);

  var xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/circular");
  T(xhr.status == 200);
  TEquals("javascript", JSON.parse(xhr.responseText).language);

  // test that we get design doc info back
  var dinfo = db.designInfo("_design/test");
  TEquals("test", dinfo.name);
  var vinfo = dinfo.view_index;
  TEquals(51, vinfo.disk_size);
  TEquals(false, vinfo.compact_running);
  TEquals("3f88e53b303e2342e49a66c538c30679", vinfo.signature);

  db.bulkSave(makeDocs(1, numDocs + 1));

  // test that the _all_docs view returns correctly with keys
  var results = db.allDocs({startkey:"_design", endkey:"_design0"});
  T(results.rows.length == 1);

  for (var loop = 0; loop < 2; loop++) {
    var rows = db.view("test/all_docs_twice").rows;
    for (var i = 0; i < numDocs; i++) {
      T(rows[2*i].key == i+1);
      T(rows[(2*i)+1].key == i+1);
    }
    T(db.view("test/no_docs").total_rows == 0)
    T(db.view("test/single_doc").total_rows == 1)
    T(db.ensureFullCommit().ok);
    restartServer();
  };

  // test when language not specified, Javascript is implied
  var designDoc2 = {
    _id:"_design/test2",
    // language: "javascript",
    views: {
      single_doc: {map: "function(doc) { if (doc._id == \"1\") { emit(1, null) }}"}
    }
  };

  T(db.save(designDoc2).ok);
  T(db.view("test2/single_doc").total_rows == 1);

  var summate = function(N) {return (N+1)*N/2;};
  var result = db.view("test/summate");
  T(result.rows[0].value == summate(numDocs));

  result = db.view("test/summate", {startkey:4,endkey:4});
  T(result.rows[0].value == 4);

  result = db.view("test/summate", {startkey:4,endkey:5});
  T(result.rows[0].value == 9);

  result = db.view("test/summate", {startkey:4,endkey:6});
  T(result.rows[0].value == 15);

  // Verify that a shared index (view def is an exact copy of "summate")
  // does not confuse the reduce stage
  result = db.view("test/summate2", {startkey:4,endkey:6});
  T(result.rows[0].value == 15);

  for(var i=1; i<numDocs/2; i+=30) {
    result = db.view("test/summate", {startkey:i,endkey:numDocs-i});
    T(result.rows[0].value == summate(numDocs-i) - summate(i-1));
  }

  T(db.deleteDoc(designDoc).ok);
  T(db.open(designDoc._id) == null);
  T(db.view("test/no_docs") == null);

  T(db.ensureFullCommit().ok);
  restartServer();
  T(db.open(designDoc._id) == null);
  T(db.view("test/no_docs") == null);

  // trigger ddoc cleanup
  T(db.viewCleanup().ok);

});
};
<commit_msg>CommonJS support in map functions<commit_after>// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

couchTests.design_docs = function(debug) {
  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
  var db2 = new CouchDB("test_suite_db_a", {"X-Couch-Full-Commit":"false"});
  db.deleteDb();
  db.createDb();
  db2.deleteDb();
  db2.createDb();
  if (debug) debugger;

  run_on_modified_server(
    [{section: "query_server_config",
      key: "reduce_limit",
      value: "false"}],
function() {

  var numDocs = 500;

  function makebigstring(power) {
    var str = "a";
    while(power-- > 0) {
      str = str + str;
    }
    return str;
  }

  var designDoc = {
    _id:"_design/test", // turn off couch.js id escaping?
    language: "javascript",
    whatever : {
      stringzone : "exports.string = 'plankton';",
      commonjs : {
        whynot : "exports.test = require('../stringzone'); exports.foo = require('whatever/stringzone');",
        upper : "exports.testing = require('./whynot').test.string.toUpperCase()+module.id+require('./whynot').foo.string"
      }
    },
    views: {
      all_docs_twice: {map: "function(doc) { emit(doc.integer, null); emit(doc.integer, null) }"},
      no_docs: {map: "function(doc) {}"},
      single_doc: {map: "function(doc) { if (doc._id == \"1\") { emit(1, null) }}"},
      summate: {map:"function (doc) {emit(doc.integer, doc.integer)};",
                reduce:"function (keys, values) { return sum(values); };"},
      summate2: {map:"function (doc) {emit(doc.integer, doc.integer)};",
                reduce:"function (keys, values) { return sum(values); };"},
      huge_src_and_results: {map: "function(doc) { if (doc._id == \"1\") { emit(\"" + makebigstring(16) + "\", null) }}",
                reduce:"function (keys, values) { return \"" + makebigstring(16) + "\"; };"},
      lib : {
        baz : "exports.baz = 'bam';",
        foo : {
          foo : "exports.foo = 'bar';",
          boom : "exports.boom = 'ok';",
          zoom : "exports.zoom = 'yeah';"
        }
      },
      commonjs : {
        map : "function(doc) { emit(null, require('views/lib/foo/boom').boom)}"
      }
    },
    shows: {
      simple: "function() {return 'ok'};",
      requirey : "function() { var lib = require('whatever/commonjs/upper'); return lib.testing; };",
      circular : "function() { var lib = require('whatever/commonjs/upper'); return JSON.stringify(this); };"
    }
  };

  var xhr = CouchDB.request("PUT", "/test_suite_db_a/_design/test", {body: JSON.stringify(designDoc)});
  var resp = JSON.parse(xhr.responseText);

  TEquals(resp.rev, db.save(designDoc).rev);

  // test that editing a show fun on the ddoc results in a change in output
  var xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/simple");
  T(xhr.status == 200);
  TEquals(xhr.responseText, "ok");

  designDoc.shows.simple = "function() {return 'ko'};"
  T(db.save(designDoc).ok);

  var xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/simple");
  T(xhr.status == 200);
  TEquals(xhr.responseText, "ko");

  var xhr = CouchDB.request("GET", "/test_suite_db_a/_design/test/_show/simple?cache=buster");
  T(xhr.status == 200);
  TEquals("ok", xhr.responseText, 'query server used wrong ddoc');

  // test commonjs require
  var xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/requirey");
  T(xhr.status == 200);
  TEquals("PLANKTONwhatever/commonjs/upperplankton", xhr.responseText);

  var xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/circular");
  T(xhr.status == 200);
  TEquals("javascript", JSON.parse(xhr.responseText).language);

  // test that we get design doc info back
  var dinfo = db.designInfo("_design/test");
  TEquals("test", dinfo.name);
  var vinfo = dinfo.view_index;
  TEquals(51, vinfo.disk_size);
  TEquals(false, vinfo.compact_running);
  TEquals("dc3264b45b74cc6d94666e3043e07154", vinfo.signature, 'ddoc sig');

  db.bulkSave(makeDocs(1, numDocs + 1));

  // test commonjs in map functions
  resp = db.view("test/commonjs", {limit:1});
  T(resp.rows[0].value == 'ok');

  // test that the _all_docs view returns correctly with keys
  var results = db.allDocs({startkey:"_design", endkey:"_design0"});
  T(results.rows.length == 1);

  for (var loop = 0; loop < 2; loop++) {
    var rows = db.view("test/all_docs_twice").rows;
    for (var i = 0; i < numDocs; i++) {
      T(rows[2*i].key == i+1);
      T(rows[(2*i)+1].key == i+1);
    }
    T(db.view("test/no_docs").total_rows == 0)
    T(db.view("test/single_doc").total_rows == 1)
    T(db.ensureFullCommit().ok);
    restartServer();
  };

  // test when language not specified, Javascript is implied
  var designDoc2 = {
    _id:"_design/test2",
    // language: "javascript",
    views: {
      single_doc: {map: "function(doc) { if (doc._id == \"1\") { emit(1, null) }}"}
    }
  };

  T(db.save(designDoc2).ok);
  T(db.view("test2/single_doc").total_rows == 1);

  var summate = function(N) {return (N+1)*N/2;};
  var result = db.view("test/summate");
  T(result.rows[0].value == summate(numDocs));

  result = db.view("test/summate", {startkey:4,endkey:4});
  T(result.rows[0].value == 4);

  result = db.view("test/summate", {startkey:4,endkey:5});
  T(result.rows[0].value == 9);

  result = db.view("test/summate", {startkey:4,endkey:6});
  T(result.rows[0].value == 15);

  // Verify that a shared index (view def is an exact copy of "summate")
  // does not confuse the reduce stage
  result = db.view("test/summate2", {startkey:4,endkey:6});
  T(result.rows[0].value == 15);

  for(var i=1; i<numDocs/2; i+=30) {
    result = db.view("test/summate", {startkey:i,endkey:numDocs-i});
    T(result.rows[0].value == summate(numDocs-i) - summate(i-1));
  }

  T(db.deleteDoc(designDoc).ok);
  T(db.open(designDoc._id) == null);
  T(db.view("test/no_docs") == null);

  T(db.ensureFullCommit().ok);
  restartServer();
  T(db.open(designDoc._id) == null);
  T(db.view("test/no_docs") == null);

  // trigger ddoc cleanup
  T(db.viewCleanup().ok);

});
};
<|endoftext|>


<commit_before>const moment = require('moment')

module.exports = {
  commands: {
    division: {
      help: 'computes moment of interruption for today\'s House of Commons business',
      command: function () {
        const now = moment()
        let timeOfInterruption = null // SO9(3) https://publications.parliament.uk/pa/cm201516/cmstords/1154/body.htm#9

        const dayOfWeek = now.day()

        switch (dayOfWeek) {
          case 1: // Mon
            timeOfInterruption = { hour: 22, minute: 0 }
            break
          case 2: // Tue
          case 3: // Wed
            timeOfInterruption = { hour: 19, minute: 0 }
            break
          case 4: // Thurs
            timeOfInterruption = { hour: 17, minute: 0 }
        }

        if (!timeOfInterruption) return 'no moment of interruption on ' + now.format('dddd')

        let dateOfInterruption = now
        dateOfInterruption.set(timeOfInterruption)

        if (dateOfInterruption.isBefore(now)) return 'moment of interruption has passed'

        return `moment of interruption is ${dateOfInterruption.fromNow()} (${dateOfInterruption.format('h:mm a')})`
      }
    },
    // the following are only valid for the 2017 to 2019 Parliamentary session:
    orderpaper: {
      aliases: [ 'op', 'cop', 'corderpaper' ],
      help: 'generates link to today\'s Commons order paper',
      command: () => {
        const ts = moment().format('YYMMDD') // e.g. OP190321.pdf
        return `https://publications.parliament.uk/pa/cm201719/cmagenda/OP${ts}.pdf`
      }
    },
    business: {
      help: 'generates link to today\'s Commons business',
      aliases: [ 'cbusiness' ],
      command: () => {
        const ts = moment().format('YYMMDD')
        return `https://publications.parliament.uk/pa/cm201719/cmagenda/ob${ts}.htm`
      }
    },
    future: {
      help: 'generates link to Commons future business',
      aliases: [ 'futurebusiness' ],
      command: () => {
        const ts = moment().format('YYMMDD')
        return `https://publications.parliament.uk/pa/cm201719/cmagenda/fb${ts}.htm`
      }
    },
    lbusiness: {
      help: 'generates link to today\'s Lords business',
      command: () => {
        const ts = moment().format('YYYY-MM-DD')
        return 'https://lordsbusiness.parliament.uk/?businessPaperDate=' + ts
      }
    }
  }
}
<commit_msg>Move Parliamentary session into constant<commit_after>const moment = require('moment')
const SESSION = '201719'

module.exports = {
  commands: {
    division: {
      help: 'computes moment of interruption for today\'s House of Commons business',
      command: function () {
        const now = moment()
        let timeOfInterruption = null // SO No. 9(3) https://publications.parliament.uk/pa/cm201516/cmstords/1154/body.htm#9

        const dayOfWeek = now.day()

        switch (dayOfWeek) {
          case 1: // Mon
            timeOfInterruption = { hour: 22, minute: 0 }
            break
          case 2: // Tue
          case 3: // Wed
            timeOfInterruption = { hour: 19, minute: 0 }
            break
          case 4: // Thurs
            timeOfInterruption = { hour: 17, minute: 0 }
        }

        if (!timeOfInterruption) return 'no moment of interruption on ' + now.format('dddd')

        let dateOfInterruption = now
        dateOfInterruption.set(timeOfInterruption)

        if (dateOfInterruption.isBefore(now)) return 'moment of interruption has passed'

        return `moment of interruption is ${dateOfInterruption.fromNow()} (${dateOfInterruption.format('h:mm a')})`
      }
    },
    // the following are only valid for the 2017 to 2019 Parliamentary session:
    orderpaper: {
      aliases: [ 'op', 'cop', 'corderpaper' ],
      help: 'generates link to today\'s Commons order paper',
      command: () => {
        const ts = moment().format('YYMMDD') // e.g. OP190321.pdf
        return `https://publications.parliament.uk/pa/cm${SESSION}/cmagenda/OP${ts}.pdf`
      }
    },
    business: {
      help: 'generates link to today\'s Commons business',
      aliases: [ 'cbusiness' ],
      command: () => {
        const ts = moment().format('YYMMDD')
        return `https://publications.parliament.uk/pa/cm${SESSION}/cmagenda/ob${ts}.htm`
      }
    },
    future: {
      help: 'generates link to Commons future business',
      aliases: [ 'futurebusiness' ],
      command: () => {
        const ts = moment().format('YYMMDD')
        return `https://publications.parliament.uk/pa/cm${SESSION}/cmagenda/fb${ts}.htm`
      }
    },
    lbusiness: {
      help: 'generates link to today\'s Lords business',
      command: () => {
        const ts = moment().format('YYYY-MM-DD')
        return 'https://lordsbusiness.parliament.uk/?businessPaperDate=' + ts
      }
    }
  }
}
<|endoftext|>


<commit_before>
/**
gestures(options, node)

Returns a stream of streams of events. Each stream of events represents the
motion of a single finger. The types of events the stream contains is either
`'mousedown'` followed by any number of `'mousemove'`s and a `'mouseup'`,
or the touch objects that go with `'touchstart'`, any number of `'touchmove'`s
and a `'touchend'`.

```js
gestures({ selector: '.thing', threshold: '0.5rem' }, document)
.each(function(events) {
    // First event is a mousedown or touchstart event
    const e0 = events.shift();

    events.each(function(e1) {
        // Mousemove or touchmove events
        const distance = Math.pow(
            Math.pow(e1.clientX - e0.clientX, 2),
            Math.pow(e1.clientY - e0.clientY, 2),
        0.5);
        ...
    });
});
```
*/

import Stream from '../../fn/modules/stream.js';
import { parseValue } from './parse-value.js';
import { isPrimaryButton, on, off } from './events.js';

export const config = {
    // Number of pixels, or string CSS length, that a pressed pointer travels
    // before gesture is started.
    threshold: 4,

    ignoreTags: {
        textarea: true,
        input: true,
        select: true,
        button: true
    }
};

var mouseevents = {
    move:   'mousemove',
    cancel: 'mouseup dragstart',
    end:    'mouseup'
};

var touchevents = {
    // Todo: why do we need passive: false? On iOS scrolling can be blocked with
    // touch-action: none... do we want to block on any arbitrary thing that we
    // gesture on or leave it to be explicitly set in CSS?
    move:   { type: 'touchmove', passive: false },
    cancel: 'touchend',
    end:    'touchend'
};

const assign = Object.assign;

function isIgnoreTag(e) {
    var tag = e.target.tagName;
    return tag && !!config.ignoreTags[tag.toLowerCase()];
}

function identifiedTouch(touchList, id) {
    var i, l;

    if (touchList.identifiedTouch) {
        return touchList.identifiedTouch(id);
    }

    // touchList.identifiedTouch() does not exist in
    // webkit yet… we must do the search ourselves...

    i = -1;
    l = touchList.length;

    while (++i < l) {
        if (touchList[i].identifier === id) {
            return touchList[i];
        }
    }
}

function changedTouch(e, data) {
    var touch = identifiedTouch(e.changedTouches, data.identifier);

    // This isn't the touch you're looking for.
    if (!touch) { return; }

    // Chrome Android (at least) includes touches that have not
    // changed in e.changedTouches. That's a bit annoying. Check
    // that this touch has changed.
    if (touch.clientX === data.clientX && touch.clientY === data.clientY) { return; }

    return touch;
}

function preventOne(e) {
    e.preventDefault();
    e.currentTarget.removeEventListener(e.type, preventOne);
}

function preventOneClick(e) {
    e.currentTarget.addEventListener('click', preventOne);
}

// Handlers that decide when the first movestart is triggered

function mousedown(e, push, options) {
    // Ignore non-primary buttons
    if (!isPrimaryButton(e)) { return; }

    // Ignore form and interactive elements
    if (isIgnoreTag(e)) { return; }

    // Check target matches selector
    if (options.selector && !e.target.closest(options.selector)) { return; }

    // Keep target around as it is redefined on the event
    // if it passes through a shadow boundary
    var event = {
        target:        e.target,
        currentTarget: e.currentTarget,
        clientX:       e.clientX,
        clientY:       e.clientY
    };

    on(mouseevents.move, mousemove, document, [event], push, options);
    on(mouseevents.cancel, mouseend, document, [event]);
}

function mousemove(e, events, push, options){
    events.push(e);
    checkThreshold(e, events, e, removeMouse, push, options);
}

function mouseend(e, data) {
    removeMouse();
}

function removeMouse() {
    off(mouseevents.move, mousemove, document);
    off(mouseevents.cancel, mouseend, document);
}

function touchstart(e, push, options) {
    // Ignore form and interactive elements
    if (isIgnoreTag(e)) { return; }

    // Check target matches selector
    if (options.selector && !e.target.closest(options.selector)) { return; }

    var touch = e.changedTouches[0];

    // iOS live updates the touch objects whereas Android gives us copies.
    // That means we can't trust the touchstart object to stay the same,
    // so we must copy the data. This object acts as a template for
    // movestart, move and moveend event objects.
    var event = {
        target:     touch.target,
        clientX:    touch.clientX,
        clientY:    touch.clientY,
        identifier: touch.identifier,

        // The only way to make handlers individually unbindable is by
        // making them unique. This is a crap place to put them, but it
        // will work.
        touchmove:  function() { touchmove.apply(this, arguments); },
        touchend:   function() { touchend.apply(this, arguments); }
    };

    on(touchevents.move, event.touchmove, document, [event], push, options);
    on(touchevents.cancel, event.touchend, document, [event]);
}

function touchmove(e, events, push, options) {
    var touch = changedTouch(e, events[0]);
    if (!touch) { return; }
    checkThreshold(e, events, touch, removeTouch, push, options);
}

function touchend(e, events) {
    var touch = identifiedTouch(e.changedTouches, events[0].identifier);
    if (!touch) { return; }
    removeTouch(events);
}

function removeTouch(events) {
    off(touchevents.move, events[0].touchmove, document);
    off(touchevents.cancel, events[0].touchend, document);
}

function checkThreshold(e, events, touch, removeHandlers, push, options) {
    var distX = touch.clientX - events[0].clientX;
    var distY = touch.clientY - events[0].clientY;
    var threshold = parseValue(options.threshold);

    // Do nothing if the threshold has not been crossed.
    if ((distX * distX) + (distY * distY) < (threshold * threshold)) {
        return;
    }

    // Unbind handlers that tracked the touch or mouse up till now.
    removeHandlers(events);
    push(touches(events[0].target, events));
}

// Handlers that control what happens following a movestart

function activeMousemove(e, data, push) {
    data.touch = e;
    data.timeStamp = e.timeStamp;
    push(e);
}

function activeMouseend(e, data, stop) {
    removeActiveMouse();
    stop();
}

function removeActiveMouse() {
    off(mouseevents.end, preventOneClick, document);
    off(mouseevents.move, activeMousemove, document);
    off(mouseevents.cancel, activeMouseend, document);
}

function activeTouchmove(e, data, push) {
    var touch = changedTouch(e, data);

    if (!touch) { return; }

    // Stop the interface from scrolling
    e.preventDefault();

    data.touch = touch;
    data.timeStamp = e.timeStamp;
    push(touch);
}

function activeTouchend(e, data, stop) {
    var touch  = identifiedTouch(e.changedTouches, data.identifier);

    // This isn't the touch you're looking for.
    if (!touch) { return; }
    removeActiveTouch(data);
    stop();
}

function removeActiveTouch(data) {
    off(touchevents.move, data.activeTouchmove, document);
    off(touchevents.end, data.activeTouchend, document);
}

function touches(node, events) {
    return events[0].identifier === undefined ?
        Stream(function MouseSource(push, stop) {
            var data = {
                target: node,
                touch: undefined
            };

            // Todo: Should Stream, perhaps, take { buffer } as a source
            // property, allowing us to return any old buffer (as long as
            // it has .shift())? Or are we happy pushing in, which causes
            // a bit of internal complexity in Stream?
            push.apply(null, events);

            // We're dealing with a mouse event.
            // Stop click from propagating at the end of a move
            on(mouseevents.end, preventOneClick, document);
            on(mouseevents.move, activeMousemove, document, data, push);
            on(mouseevents.cancel, activeMouseend, document, data, stop);

            return {
                stop: function() {
                    removeActiveMouse();
                    stop();
                }
            };
        }):

        Stream(function TouchSource(push, stop) {
            var data = {
                target: node,
                touch: undefined,
                identifier: events[0].identifier
            };

            push.apply(null, events);

            // Track a touch
            // In order to unbind correct handlers they have to be unique
            data.activeTouchmove = function (e) { activeTouchmove(e, data, push); };
            data.activeTouchend = function (e) { activeTouchend(e, data, stop); };

            // We're dealing with a touch.
            on(touchevents.move, data.activeTouchmove, document);
            on(touchevents.end, data.activeTouchend, document);

            return {
                stop: function () {
                    removeActiveTouch(data);
                    stop();
                }
            };
        });
}

export default function gestures(options, node) {
    // Support legacy signature gestures(node)
    if (!node) {
        console.trace('Deprecated gestures(node), now gestures(options, node)');
    }

    options = node ?
        options ? assign({}, config, options) : config :
        config ;
    node = node ?
        node :
        options ;

    return new Stream(function(push, stop) {
        function mouseHandler(e) {
            mousedown(e, push, options);
        }

        function touchHandler(e) {
            touchstart(e, push, options);
        }

        on('mousedown', mouseHandler, node);
        on('touchstart', touchHandler, node);

        return {
            stop: function() {
                off('mousedown', mouseHandler, node);
                off('touchstart', touchHandler, node);
                stop();
            }
        };
    });
}
<commit_msg>Gesture<commit_after>
/**
gestures(options, node)

Returns a stream of streams of events. Each stream of events represents the
motion of a single finger. The types of events the stream contains is either
`'mousedown'` followed by any number of `'mousemove'`s and a `'mouseup'`,
or the touch objects that go with `'touchstart'`, any number of `'touchmove'`s
and a `'touchend'`.

```js
gestures({ selector: '.thing', threshold: '0.5rem' }, document)
.each(function(events) {
    // First event is a mousedown or touchstart event
    const e0 = events.shift();

    events.each(function(e1) {
        // Mousemove or touchmove events
        const distance = Math.pow(
            Math.pow(e1.clientX - e0.clientX, 2),
            Math.pow(e1.clientY - e0.clientY, 2),
        0.5);
        ...
    });
});
```
*/

import Stream from '../../fn/modules/stream.js';
import { parseValue } from './parse-value.js';
import { isPrimaryButton, on, off } from './events.js';

export const config = {
    // Number of pixels, or string CSS length, that a pressed pointer travels
    // before gesture is started.
    threshold: 4,

    ignoreTags: {
        textarea: true,
        input: true,
        select: true,
        button: true
    }
};

var mouseevents = {
    move:   'mousemove',
    cancel: 'mouseup dragstart',
    end:    'mouseup'
};

var touchevents = {
    // Todo: why do we need passive: false? On iOS scrolling can be blocked with
    // touch-action: none... do we want to block on any arbitrary thing that we
    // gesture on or leave it to be explicitly set in CSS?
    move:   { type: 'touchmove', passive: false },
    cancel: 'touchend',
    end:    { type: 'touchend', passive: false }
};

const assign = Object.assign;

function isIgnoreTag(e) {
    var tag = e.target.tagName;
    return tag && !!config.ignoreTags[tag.toLowerCase()];
}

function identifiedTouch(touchList, id) {
    var i, l;

    if (touchList.identifiedTouch) {
        return touchList.identifiedTouch(id);
    }

    // touchList.identifiedTouch() does not exist in
    // webkit yet… we must do the search ourselves...

    i = -1;
    l = touchList.length;

    while (++i < l) {
        if (touchList[i].identifier === id) {
            return touchList[i];
        }
    }
}

function changedTouch(e, data) {
    var touch = identifiedTouch(e.changedTouches, data.identifier);

    // This isn't the touch you're looking for.
    if (!touch) { return; }

    // Chrome Android (at least) includes touches that have not
    // changed in e.changedTouches. That's a bit annoying. Check
    // that this touch has changed.
    if (touch.clientX === data.clientX && touch.clientY === data.clientY) { return; }

    return touch;
}

function preventOne(e) {
    e.preventDefault();
    e.currentTarget.removeEventListener(e.type, preventOne);
}

function preventOneClick(e) {
    e.currentTarget.addEventListener('click', preventOne);
}

// Handlers that decide when the first movestart is triggered

function mousedown(e, push, options) {
    // Ignore non-primary buttons
    if (!isPrimaryButton(e)) { return; }

    // Ignore form and interactive elements
    if (isIgnoreTag(e)) { return; }

    // Check target matches selector
    if (options.selector && !e.target.closest(options.selector)) { return; }

    // Keep target around as it is redefined on the event
    // if it passes through a shadow boundary
    var event = {
        target:        e.target,
        currentTarget: e.currentTarget,
        clientX:       e.clientX,
        clientY:       e.clientY
    };

    on(mouseevents.move, mousemove, document, [event], push, options);
    on(mouseevents.cancel, mouseend, document, [event]);
}

function mousemove(e, events, push, options){
    events.push(e);
    checkThreshold(e, events, e, removeMouse, push, options);
}

function mouseend(e, data) {
    removeMouse();
}

function removeMouse() {
    off(mouseevents.move, mousemove, document);
    off(mouseevents.cancel, mouseend, document);
}

function touchstart(e, push, options) {
    // Ignore form and interactive elements
    if (isIgnoreTag(e)) { return; }

    // Check target matches selector
    if (options.selector && !e.target.closest(options.selector)) { return; }

    var touch = e.changedTouches[0];

    // iOS live updates the touch objects whereas Android gives us copies.
    // That means we can't trust the touchstart object to stay the same,
    // so we must copy the data. This object acts as a template for
    // movestart, move and moveend event objects.
    var event = {
        target:     touch.target,
        clientX:    touch.clientX,
        clientY:    touch.clientY,
        identifier: touch.identifier,

        // The only way to make handlers individually unbindable is by
        // making them unique. This is a crap place to put them, but it
        // will work.
        touchmove:  function() { touchmove.apply(this, arguments); },
        touchend:   function() { touchend.apply(this, arguments); }
    };

    on(touchevents.move, event.touchmove, document, [event], push, options);
    on(touchevents.cancel, event.touchend, document, [event]);
}

function touchmove(e, events, push, options) {
    var touch = changedTouch(e, events[0]);
    if (!touch) { return; }
    checkThreshold(e, events, touch, removeTouch, push, options);
}

function touchend(e, events) {
    var touch = identifiedTouch(e.changedTouches, events[0].identifier);
    if (!touch) { return; }
    removeTouch(events);
}

function removeTouch(events) {
    off(touchevents.move, events[0].touchmove, document);
    off(touchevents.cancel, events[0].touchend, document);
}

function checkThreshold(e, events, touch, removeHandlers, push, options) {
    var distX = touch.clientX - events[0].clientX;
    var distY = touch.clientY - events[0].clientY;
    var threshold = parseValue(options.threshold);

    // Do nothing if the threshold has not been crossed.
    if ((distX * distX) + (distY * distY) < (threshold * threshold)) {
        return;
    }

    // Unbind handlers that tracked the touch or mouse up till now.
    removeHandlers(events);
    push(touches(events[0].target, events));
}

// Handlers that control what happens following a movestart

function activeMousemove(e, data, push) {
    data.touch = e;
    data.timeStamp = e.timeStamp;
    push(e);
}

function activeMouseend(e, data, stop) {
    removeActiveMouse();
    stop();
}

function removeActiveMouse() {
    off(mouseevents.end, preventOneClick, document);
    off(mouseevents.move, activeMousemove, document);
    off(mouseevents.cancel, activeMouseend, document);
}

function activeTouchmove(e, data, push) {
    var touch = changedTouch(e, data);

    if (!touch) { return; }

    // Stop the interface from scrolling
    e.preventDefault();

    data.touch = touch;
    data.timeStamp = e.timeStamp;
    push(touch);
}

function activeTouchend(e, data, stop) {
    var touch  = identifiedTouch(e.changedTouches, data.identifier);

    // This isn't the touch you're looking for.
    if (!touch) { return; }

    // Neuter the resulting click event
    e.preventDefault();

    removeActiveTouch(data);
    stop();
}

function removeActiveTouch(data) {
    off(touchevents.move, data.activeTouchmove, document);
    off(touchevents.end, data.activeTouchend, document);
}

function touches(node, events) {
    return events[0].identifier === undefined ?
        Stream(function MouseSource(push, stop) {
            var data = {
                target: node,
                touch: undefined
            };

            // Todo: Should Stream, perhaps, take { buffer } as a source
            // property, allowing us to return any old buffer (as long as
            // it has .shift())? Or are we happy pushing in, which causes
            // a bit of internal complexity in Stream?
            push.apply(null, events);

            // We're dealing with a mouse event.
            // Stop click from propagating at the end of a move
            on(mouseevents.end, preventOneClick, document);
            on(mouseevents.move, activeMousemove, document, data, push);
            on(mouseevents.cancel, activeMouseend, document, data, stop);

            return {
                stop: function() {
                    removeActiveMouse();
                    stop();
                }
            };
        }):

        Stream(function TouchSource(push, stop) {
            var data = {
                target: node,
                touch: undefined,
                identifier: events[0].identifier
            };

            push.apply(null, events);

            // Track a touch
            // In order to unbind correct handlers they have to be unique
            data.activeTouchmove = function (e) { activeTouchmove(e, data, push); };
            data.activeTouchend = function (e) { activeTouchend(e, data, stop); };

            // We're dealing with a touch.
            on(touchevents.move, data.activeTouchmove, document);
            on(touchevents.end, data.activeTouchend, document);

            return {
                stop: function () {
                    removeActiveTouch(data);
                    stop();
                }
            };
        });
}

export default function gestures(options, node) {
    // Support legacy signature gestures(node)
    if (!node) {
        console.trace('Deprecated gestures(node), now gestures(options, node)');
    }

    options = node ?
        options ? assign({}, config, options) : config :
        config ;
    node = node ?
        node :
        options ;

    return new Stream(function(push, stop) {
        function mouseHandler(e) {
            mousedown(e, push, options);
        }

        function touchHandler(e) {
            touchstart(e, push, options);
        }

        on('mousedown', mouseHandler, node);
        on('touchstart', touchHandler, node);

        return {
            stop: function() {
                off('mousedown', mouseHandler, node);
                off('touchstart', touchHandler, node);
                stop();
            }
        };
    });
}
<|endoftext|>


<commit_before>import clone from 'clone';
import equal from 'deep-equal';
import extend from 'extend';
import Delta from 'rich-text/lib/delta';
import Parchment from 'parchment';
import Quill from '../core/quill';
import logger from '../core/logger';
import Module from '../core/module';
import { Range } from '../core/selection';
import Block from '../blots/block';

let debug = logger('quill:keyboard');

class Keyboard extends Module {
  static match(evt, binding) {
    binding = normalize(binding);
    let metaKey = /Mac/i.test(navigator.platform) ? evt.metaKey : evt.metaKey || evt.ctrlKey;
    if (!!binding.metaKey !== metaKey && binding.metaKey !== null) return false;
    if (!!binding.shiftKey !== evt.shiftKey && binding.shiftKey !== null) return false;
    if (!!binding.altKey !== evt.altKey && binding.altKey !== null) return false;
    return binding.key === (evt.which || evt.keyCode);
  }

  constructor(quill, options) {
    super(quill, options);
    this.options.bindings = extend({}, Keyboard.DEFAULTS.bindings, options.bindings);
    this.bindings = {};
    Object.keys(this.options.bindings).forEach((name) => {
      let [key, context, handler] = this.options.bindings[name];
      this.addBinding(key, context, handler);
    });
    this.addBinding({ key: Keyboard.keys.ENTER, shiftKey: null }, handleEnter);
    this.addBinding({ key: Keyboard.keys.BACKSPACE }, makeDeleteHanlder(true));
    this.addBinding({ key: Keyboard.keys.DELETE }, makeDeleteHanlder(false));
    this.listen();
  }

  addBinding(binding, context, handler) {
    binding = normalize(binding);
    if (binding == null) {
      return debug.warn('Attempted to add invalid keyboard binding', binding);
    }
    if (typeof context === 'function') {
      handler = context;
      context = {};
    }
    this.bindings[binding.key] = this.bindings[binding.key] || [];
    this.bindings[binding.key].push([binding, context, handler]);
  }

  listen() {
    this.quill.root.addEventListener('keydown', (evt) => {
      if (evt.defaultPrevented) return;
      let which = evt.which || evt.keyCode;
      let bindings = (this.bindings[which] || []).filter(function(tuple) {
        return Keyboard.match(evt, tuple[0]);
      });
      if (bindings.length === 0) return;
      let range = this.quill.getSelection();
      if (range == null) return;    // implies we do not have focus
      let format = null;
      let prevented = !bindings.every((tuple) => {
        let [key, context, handler] = tuple;
        if (context.collapsed === true && range.length > 0) return true;
        if (context.collapsed === false && range.length === 0) return true;
        if (context.format) {
          format = format || this.quill.getFormat(range);
          let formatsMatch = Object.keys(context.format).every(function(name) {
            return (context.format[name] === true && format[name] != null) ||
                   (context.format[name] === false && format[name] == null) ||
                   (equal(context.format[name], format[name]));
          });
          if (!formatsMatch) return true;
        }
        return handler.call(this, range);
      });
      if (prevented) {
        evt.preventDefault();
      }
    });
  }
}

Keyboard.keys = {
  BACKSPACE: 8,
  TAB: 9,
  ENTER: 13,
  ESCAPE: 27,
  LEFT: 37,
  UP: 38,
  RIGHT: 39,
  DOWN: 40,
  DELETE: 46
};

Keyboard.DEFAULTS = {
  bindings: {
    'add bold'          : makeFormatHandler('bold', true),
    'add italic'        : makeFormatHandler('italic', true),
    'add underline'     : makeFormatHandler('underline', true),
    'remove bold'       : makeFormatHandler('bold', false),
    'remove italic'     : makeFormatHandler('italic', false),
    'remove underline'  : makeFormatHandler('underline', false),
    'indent'            : makeIndentHanlder(true),
    'outdent'           : makeIndentHanlder(false),
    'tab'               : [
      { key: Keyboard.keys.TAB, shiftKey: null },
      { collapsed: true },
      function(range) {
        this.quill.insertText(range.index, '\t', Quill.sources.USER);
        this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
        return false;
      }
    ]
  }
};

function handleEnter(range) {
  let formats = this.quill.getFormat(range);
  let lineFormats = Object.keys(formats).reduce(function(lineFormats, format) {
    if (Parchment.query(format, Parchment.Scope.BLOCK)) {
      lineFormats[format] = formats[format];
    }
    return lineFormats;
  }, {});
  let added = 1;
  let delta = new Delta()
    .retain(range.index)
    .insert('\n', lineFormats);
  delta.delete(range.length);
  this.quill.updateContents(delta, Quill.sources.USER);
  this.quill.setSelection(range.index + added, Quill.sources.SILENT);
  Object.keys(formats).forEach((name) => {
    if (lineFormats[name] == null) {
      this.quill.format(name, formats[name]);
    }
  });
  this.quill.selection.scrollIntoView();
  return false;
}

function makeDeleteHanlder(isBackspace) {
  return function(range) {
    if (range.length > 0) {
      this.quill.deleteText(range, Quill.sources.USER);
    } else if (!isBackspace) {
      this.quill.deleteText(range.index, 1, Quill.sources.USER);
    } else {
      let [line, offset] = this.quill.scroll.descendant(Block, range.index);
      let formats = this.quill.getFormat(range);
      if (line != null && offset === 0 && (formats['indent'] || formats['list'])) {
        if (formats['indent'] != null) {
          line.format('indent', parseInt(formats['indent']) - 1, Quill.sources.USER);
        } else {
          line.format('list', false);
        }
      } else {
        this.quill.deleteText(range.index - 1, 1, Quill.sources.USER);
        range = new Range(Math.max(0, range.index - 1));
      }
    }
    this.quill.setSelection(range.index, Quill.sources.SILENT);
  }
}

function makeFormatHandler(format, value) {
  let key = { key: format[0].toUpperCase(), metaKey: true };
  let context = {
    format: { [format]: !value }
  };
  let handler = function(range) {
    this.quill.format(format, value, Quill.sources.USER);
    return false;
  };
  return [key, context, handler];
}

function makeIndentHanlder(indent) {
  let handler = function(range) {
    let modifier = indent ? 1 : -1;
    this.quill.scroll.descendants(Block, range.index, range.length).forEach(function(line) {
      let format = line.formats();
      let level = parseInt(format['indent'] || 0);
      line.format('indent', Math.max(0, level + modifier));
    });
    this.quill.update(Quill.sources.USER);
    this.quill.setSelection(range, Quill.sources.SILENT);
    return false;
  };
  return [{ key: Keyboard.keys.TAB, shiftKey: !indent }, { collapsed: false }, handler];
}

function normalize(binding) {
  switch (typeof binding) {
    case 'string':
      if (Keyboard.keys[binding.toUpperCase()] != null) {
        binding = { key: Keyboard.keys[binding.toUpperCase()] };
      } else if (binding.length === 1) {
        binding = { key: binding.toUpperCase().charCodeAt(0) };
      } else {
        return null;
      }
      break;
    case 'number':
      binding = { key: binding };
      break;
    case 'object':
      binding = clone(binding, false);
      break;
    default:
      return null;
  }
  if (typeof binding.key === 'string') {
    binding.key = binding.key.toUpperCase().charCodeAt(0);
  }
  return binding;
}

export default Keyboard;
<commit_msg>prevent continue calling handlers by default<commit_after>import clone from 'clone';
import equal from 'deep-equal';
import extend from 'extend';
import Delta from 'rich-text/lib/delta';
import Parchment from 'parchment';
import Quill from '../core/quill';
import logger from '../core/logger';
import Module from '../core/module';
import { Range } from '../core/selection';
import Block from '../blots/block';

let debug = logger('quill:keyboard');

class Keyboard extends Module {
  static match(evt, binding) {
    binding = normalize(binding);
    let metaKey = /Mac/i.test(navigator.platform) ? evt.metaKey : evt.metaKey || evt.ctrlKey;
    if (!!binding.metaKey !== metaKey && binding.metaKey !== null) return false;
    if (!!binding.shiftKey !== evt.shiftKey && binding.shiftKey !== null) return false;
    if (!!binding.altKey !== evt.altKey && binding.altKey !== null) return false;
    return binding.key === (evt.which || evt.keyCode);
  }

  constructor(quill, options) {
    super(quill, options);
    this.options.bindings = extend({}, Keyboard.DEFAULTS.bindings, options.bindings);
    this.bindings = {};
    Object.keys(this.options.bindings).forEach((name) => {
      let [key, context, handler] = this.options.bindings[name];
      this.addBinding(key, context, handler);
    });
    this.addBinding({ key: Keyboard.keys.ENTER, shiftKey: null }, handleEnter);
    this.addBinding({ key: Keyboard.keys.BACKSPACE }, makeDeleteHanlder(true));
    this.addBinding({ key: Keyboard.keys.DELETE }, makeDeleteHanlder(false));
    this.listen();
  }

  addBinding(binding, context, handler) {
    binding = normalize(binding);
    if (binding == null) {
      return debug.warn('Attempted to add invalid keyboard binding', binding);
    }
    if (typeof context === 'function') {
      handler = context;
      context = {};
    }
    this.bindings[binding.key] = this.bindings[binding.key] || [];
    this.bindings[binding.key].push([binding, context, handler]);
  }

  listen() {
    this.quill.root.addEventListener('keydown', (evt) => {
      if (evt.defaultPrevented) return;
      let which = evt.which || evt.keyCode;
      let bindings = (this.bindings[which] || []).filter(function(tuple) {
        return Keyboard.match(evt, tuple[0]);
      });
      if (bindings.length === 0) return;
      let range = this.quill.getSelection();
      if (range == null) return;    // implies we do not have focus
      let format = null;
      let prevented = bindings.some((tuple) => {
        let [key, context, handler] = tuple;
        if (context.collapsed === true && range.length > 0) return false;
        if (context.collapsed === false && range.length === 0) return false;
        if (context.format) {
          format = format || this.quill.getFormat(range);
          let formatsMatch = Object.keys(context.format).every(function(name) {
            return (context.format[name] === true && format[name] != null) ||
                   (context.format[name] === false && format[name] == null) ||
                   (equal(context.format[name], format[name]));
          });
          if (!formatsMatch) return false;
        }
        return handler.call(this, range) !== true;
      });
      if (prevented) {
        evt.preventDefault();
      }
    });
  }
}

Keyboard.keys = {
  BACKSPACE: 8,
  TAB: 9,
  ENTER: 13,
  ESCAPE: 27,
  LEFT: 37,
  UP: 38,
  RIGHT: 39,
  DOWN: 40,
  DELETE: 46
};

Keyboard.DEFAULTS = {
  bindings: {
    'add bold'          : makeFormatHandler('bold', true),
    'add italic'        : makeFormatHandler('italic', true),
    'add underline'     : makeFormatHandler('underline', true),
    'remove bold'       : makeFormatHandler('bold', false),
    'remove italic'     : makeFormatHandler('italic', false),
    'remove underline'  : makeFormatHandler('underline', false),
    'indent'            : makeIndentHanlder(true),
    'outdent'           : makeIndentHanlder(false),
    'tab'               : [
      { key: Keyboard.keys.TAB, shiftKey: null },
      { collapsed: true },
      function(range) {
        this.quill.insertText(range.index, '\t', Quill.sources.USER);
        this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
        return false;
      }
    ]
  }
};

function handleEnter(range) {
  let formats = this.quill.getFormat(range);
  let lineFormats = Object.keys(formats).reduce(function(lineFormats, format) {
    if (Parchment.query(format, Parchment.Scope.BLOCK)) {
      lineFormats[format] = formats[format];
    }
    return lineFormats;
  }, {});
  let added = 1;
  let delta = new Delta()
    .retain(range.index)
    .insert('\n', lineFormats);
  delta.delete(range.length);
  this.quill.updateContents(delta, Quill.sources.USER);
  this.quill.setSelection(range.index + added, Quill.sources.SILENT);
  Object.keys(formats).forEach((name) => {
    if (lineFormats[name] == null) {
      this.quill.format(name, formats[name]);
    }
  });
  this.quill.selection.scrollIntoView();
  return false;
}

function makeDeleteHanlder(isBackspace) {
  return function(range) {
    if (range.length > 0) {
      this.quill.deleteText(range, Quill.sources.USER);
    } else if (!isBackspace) {
      this.quill.deleteText(range.index, 1, Quill.sources.USER);
    } else {
      let [line, offset] = this.quill.scroll.descendant(Block, range.index);
      let formats = this.quill.getFormat(range);
      if (line != null && offset === 0 && (formats['indent'] || formats['list'])) {
        if (formats['indent'] != null) {
          line.format('indent', parseInt(formats['indent']) - 1, Quill.sources.USER);
        } else {
          line.format('list', false);
        }
      } else {
        this.quill.deleteText(range.index - 1, 1, Quill.sources.USER);
        range = new Range(Math.max(0, range.index - 1));
      }
    }
    this.quill.setSelection(range.index, Quill.sources.SILENT);
  }
}

function makeFormatHandler(format, value) {
  let key = { key: format[0].toUpperCase(), metaKey: true };
  let context = {
    format: { [format]: !value }
  };
  let handler = function(range) {
    this.quill.format(format, value, Quill.sources.USER);
    return false;
  };
  return [key, context, handler];
}

function makeIndentHanlder(indent) {
  let handler = function(range) {
    let modifier = indent ? 1 : -1;
    this.quill.scroll.descendants(Block, range.index, range.length).forEach(function(line) {
      let format = line.formats();
      let level = parseInt(format['indent'] || 0);
      line.format('indent', Math.max(0, level + modifier));
    });
    this.quill.update(Quill.sources.USER);
    this.quill.setSelection(range, Quill.sources.SILENT);
    return false;
  };
  return [{ key: Keyboard.keys.TAB, shiftKey: !indent }, { collapsed: false }, handler];
}

function normalize(binding) {
  switch (typeof binding) {
    case 'string':
      if (Keyboard.keys[binding.toUpperCase()] != null) {
        binding = { key: Keyboard.keys[binding.toUpperCase()] };
      } else if (binding.length === 1) {
        binding = { key: binding.toUpperCase().charCodeAt(0) };
      } else {
        return null;
      }
      break;
    case 'number':
      binding = { key: binding };
      break;
    case 'object':
      binding = clone(binding, false);
      break;
    default:
      return null;
  }
  if (typeof binding.key === 'string') {
    binding.key = binding.key.toUpperCase().charCodeAt(0);
  }
  return binding;
}

export default Keyboard;
<|endoftext|>


<commit_before>/**
 * @fileOverview XMLHttpRequest wrapper module for restartless addons
 * @author       YUKI "Piro" Hiroshi
 * @version      7
 * @description
 *   // get as a text
 *   http.get('http://.....',
 *            { 'X-Response-Content-Type': 'text/plain; charset=EUC-JP' })
 *       .next(function(aResponse) {
 *         console.log(aResponse.responseText);
 *       });
 *
 *   // get as a JSON
 *   http.getAsJSON('http://.....')
 *       .next(function(aResponse) {
 *         var body = aResponse.response;
 *         console.log(function);
 *       });
 *
 *   // get as a binary
 *   http.getAsBinary('http://.....')
 *       .next(function(aResponse) {
 *         // arraybyffer = aResponse.response;
 *         var bodyBytesArray = aResponse.responseText;
 *         var bodyBase64 = btoa(bodyBytesArray);
 *         console.log(bodyBase64);
 *       });
 *   // See also: https://developer.mozilla.org/docs/XMLHttpRequest/Sending_and_Receiving_Binary_Data
 *
 *   http.postAsJSON('http://.....', { a: true, b: 29 })
 *       .next(function(aResponse) {
 *       });
 *
 * @license
 *   The MIT License, Copyright (c) 2014 YUKI "Piro" Hiroshi.
 *   https://github.com/piroor/restartless/blob/master/license.txt
 * @url http://github.com/piroor/restartless
 */

var EXPORTED_SYMBOLS = [
  'get',
  'getAsJSON',
  'getAsBinary',
  'post',
  'postAsJSON',
  'RESPONSE_TYPE',
  'RESPONSE_CONTENT_TYPE'
];

var RESPONSE_TYPE         = 'X-Response-Type';
var RESPONSE_CONTENT_TYPE = 'X-Response-Content-Type';

var PSEUDO_HEADERS = [
  RESPONSE_TYPE,
  RESPONSE_CONTENT_TYPE
];

var Deferred = require('jsdeferred').Deferred;

function clone(aObject) {
  aObject = aObject || {};
  var cloned = {};
  Object.keys(aObject).forEach(function(aKey) {
    cloned[aKey] = aObject[aKey];
  });
  return cloned;
}

function get(aURI, aHeaders) {
  return sendRequest({
    method:  'GET',
    uri:     aURI,
    headers: aHeaders
  });
}

function getAsJSON(aURI, aHeaders) {
  var headers = clone(aHeaders);
  headers[RESPONSE_TYPE] = 'json';
  return get(aURI, headers);
}

function getAsBinary(aURI, aHeaders) {
  var headers = clone(aHeaders);
  headers[RESPONSE_TYPE] = 'arraybuffer';
  return get(aURI, headers);
}

function post(aURI, aPostData, aHeaders) {
  return sendRequest({
    method:   'POST',
    uri:      aURI,
    headers:  aHeaders,
    postData: aPostData
  });
}

function postAsJSON(aURI, aPostData, aHeaders) {
  var headers = clone(aHeaders);
  var postData = JSON.stringify(aPostData);
  headers['Content-Type'] = 'application/json';
  return post(aURI, aPostData, headers);
}

function ArrayBufferRespone(aResponse) {
  this._raw = aResponse;
  this._init();
}
ArrayBufferRespone.prototype = {
  _init: function() {
    for (let key in this._raw) {
      if (key in this)
        continue;
      this._bind(key);
    }
  },
  _bind: function(aName) {
    if (typeof this._raw[aName] == 'function') {
      this[aName] = this._raw[aName].bind(this._raw);
    } else {
      this.__defineGetter__(aName, function() {
        return this._raw[aName];
      });
    }
  },
  get responseText() {
    var bytesArray = ''
    var bytes = new Uint8Array(this._raw.response);
    for (let i = 0, maxi = bytes.byteLength; i < maxi; i++) {
      bytesArray += String.fromCharCode(bytes[i]);
    }
    return bytesArrya;
  },
  get responseXML() {
    // accessing to the property raises InvalidStateError, so I have to define this staticly
    return this._raw.responseXML;
  }
};

function sendRequest(aParams) {
  var deferred = new Deferred();

  var method   = (aParams.method || 'GET').toUpperCase();
  var uri      = aParams.uri || null;
  var headers  = aParams.headers || {};
  var postData = aParams.postData || null;
  var responseType = aParams.responseType || '';
  var responseContentType = aParams.responseContentType || null;

  if (headers[RESPONSE_TYPE])
    responseType = headers[RESPONSE_TYPE];
  if (headers[RESPONSE_CONTENT_TYPE])
    responseContentType = headers[RESPONSE_CONTENT_TYPE];

  if (!uri)
    throw new Error('no URL');

  var request;
  var listener = function(aEvent) {
    switch (aEvent.type) {
      case 'load':
      case 'error':
        break;
      default:
        return;
    }
    request.removeEventListener('load', listener, false);
    request.removeEventListener('error', listener, false);

    var response = request;
    if (responseType == 'arraybuffer')
      response = new ArrayBufferRespone(response)

    deferred.call(response);
  };

  Deferred.next(function() {
    request = Cc['@mozilla.org/xmlextras/xmlhttprequest;1']
                .createInstance(Ci.nsIXMLHttpRequest)
                .QueryInterface(Ci.nsIDOMEventTarget);
    request.open(method, uri, true);
    if (responseType)
      request.responseType = responseType;
    if (responseContentType)
      request.overrideMimeType(responseContentType);
    Object.keys(headers).forEach(function(aKey) {
      if (PSEUDO_HEADERS.indexOf(aKey) < 0)
        request.setRequestHeader(aKey, headers[aKey]);
    });
    request.addEventListener('load', listener, false);
    request.addEventListener('error', listener, false);
    request.send(postData);
  }).error(function(aError) {
    deferred.fail(aError);
  });

  return deferred;
}
<commit_msg>Fix typo<commit_after>/**
 * @fileOverview XMLHttpRequest wrapper module for restartless addons
 * @author       YUKI "Piro" Hiroshi
 * @version      7
 * @description
 *   // get as a text
 *   http.get('http://.....',
 *            { 'X-Response-Content-Type': 'text/plain; charset=EUC-JP' })
 *       .next(function(aResponse) {
 *         console.log(aResponse.responseText);
 *       });
 *
 *   // get as a JSON
 *   http.getAsJSON('http://.....')
 *       .next(function(aResponse) {
 *         var body = aResponse.response;
 *         console.log(function);
 *       });
 *
 *   // get as a binary
 *   http.getAsBinary('http://.....')
 *       .next(function(aResponse) {
 *         // arraybyffer = aResponse.response;
 *         var bodyBytesArray = aResponse.responseText;
 *         var bodyBase64 = btoa(bodyBytesArray);
 *         console.log(bodyBase64);
 *       });
 *   // See also: https://developer.mozilla.org/docs/XMLHttpRequest/Sending_and_Receiving_Binary_Data
 *
 *   http.postAsJSON('http://.....', { a: true, b: 29 })
 *       .next(function(aResponse) {
 *       });
 *
 * @license
 *   The MIT License, Copyright (c) 2014 YUKI "Piro" Hiroshi.
 *   https://github.com/piroor/restartless/blob/master/license.txt
 * @url http://github.com/piroor/restartless
 */

var EXPORTED_SYMBOLS = [
  'get',
  'getAsJSON',
  'getAsBinary',
  'post',
  'postAsJSON',
  'RESPONSE_TYPE',
  'RESPONSE_CONTENT_TYPE'
];

var RESPONSE_TYPE         = 'X-Response-Type';
var RESPONSE_CONTENT_TYPE = 'X-Response-Content-Type';

var PSEUDO_HEADERS = [
  RESPONSE_TYPE,
  RESPONSE_CONTENT_TYPE
];

var Deferred = require('jsdeferred').Deferred;

function clone(aObject) {
  aObject = aObject || {};
  var cloned = {};
  Object.keys(aObject).forEach(function(aKey) {
    cloned[aKey] = aObject[aKey];
  });
  return cloned;
}

function get(aURI, aHeaders) {
  return sendRequest({
    method:  'GET',
    uri:     aURI,
    headers: aHeaders
  });
}

function getAsJSON(aURI, aHeaders) {
  var headers = clone(aHeaders);
  headers[RESPONSE_TYPE] = 'json';
  return get(aURI, headers);
}

function getAsBinary(aURI, aHeaders) {
  var headers = clone(aHeaders);
  headers[RESPONSE_TYPE] = 'arraybuffer';
  return get(aURI, headers);
}

function post(aURI, aPostData, aHeaders) {
  return sendRequest({
    method:   'POST',
    uri:      aURI,
    headers:  aHeaders,
    postData: aPostData
  });
}

function postAsJSON(aURI, aPostData, aHeaders) {
  var headers = clone(aHeaders);
  var postData = JSON.stringify(aPostData);
  headers['Content-Type'] = 'application/json';
  return post(aURI, aPostData, headers);
}

function ArrayBufferRespone(aResponse) {
  this._raw = aResponse;
  this._init();
}
ArrayBufferRespone.prototype = {
  _init: function() {
    for (let key in this._raw) {
      if (key in this)
        continue;
      this._bind(key);
    }
  },
  _bind: function(aName) {
    if (typeof this._raw[aName] == 'function') {
      this[aName] = this._raw[aName].bind(this._raw);
    } else {
      this.__defineGetter__(aName, function() {
        return this._raw[aName];
      });
    }
  },
  get responseText() {
    var bytesArray = ''
    var bytes = new Uint8Array(this._raw.response);
    for (let i = 0, maxi = bytes.byteLength; i < maxi; i++) {
      bytesArray += String.fromCharCode(bytes[i]);
    }
    return bytesArray;
  },
  get responseXML() {
    // accessing to the property raises InvalidStateError, so I have to define this staticly
    return this._raw.responseXML;
  }
};

function sendRequest(aParams) {
  var deferred = new Deferred();

  var method   = (aParams.method || 'GET').toUpperCase();
  var uri      = aParams.uri || null;
  var headers  = aParams.headers || {};
  var postData = aParams.postData || null;
  var responseType = aParams.responseType || '';
  var responseContentType = aParams.responseContentType || null;

  if (headers[RESPONSE_TYPE])
    responseType = headers[RESPONSE_TYPE];
  if (headers[RESPONSE_CONTENT_TYPE])
    responseContentType = headers[RESPONSE_CONTENT_TYPE];

  if (!uri)
    throw new Error('no URL');

  var request;
  var listener = function(aEvent) {
    switch (aEvent.type) {
      case 'load':
      case 'error':
        break;
      default:
        return;
    }
    request.removeEventListener('load', listener, false);
    request.removeEventListener('error', listener, false);

    var response = request;
    if (responseType == 'arraybuffer')
      response = new ArrayBufferRespone(response)

    deferred.call(response);
  };

  Deferred.next(function() {
    request = Cc['@mozilla.org/xmlextras/xmlhttprequest;1']
                .createInstance(Ci.nsIXMLHttpRequest)
                .QueryInterface(Ci.nsIDOMEventTarget);
    request.open(method, uri, true);
    if (responseType)
      request.responseType = responseType;
    if (responseContentType)
      request.overrideMimeType(responseContentType);
    Object.keys(headers).forEach(function(aKey) {
      if (PSEUDO_HEADERS.indexOf(aKey) < 0)
        request.setRequestHeader(aKey, headers[aKey]);
    });
    request.addEventListener('load', listener, false);
    request.addEventListener('error', listener, false);
    request.send(postData);
  }).error(function(aError) {
    deferred.fail(aError);
  });

  return deferred;
}
<|endoftext|>


<commit_before>/**
 * Provides utilities to traverse in memory code and files for main and test parsing generating DocObject and AST data.
 */
export default class TraverseUtil
{
   /**
    * Provides eventbus bindings for traversing in memory code and files for main and test parsing generating DocObject
    * and AST data.
    *
    * @param {PluginEvent}    ev - The plugin eventbus proxy.
    */
   onPluginLoad(ev)
   {
      /**
       * Stores the plugin eventbus proxy.
       * @type {EventProxy}
       */
      this._eventbus = ev.eventbus;

      this._eventbus.on('tjsdoc:traverse:code', this.traverseCode, this);
      this._eventbus.on('tjsdoc:traverse:file', this.traverseFile, this);
      this._eventbus.on('tjsdoc:traverse:test', this.traverseTest, this);
   }

   /**
    * Traverse doc comments in given code.
    *
    * @param {string}      inDirPath - root directory path.
    *
    * @param {object|string}  code - target JavaScript code.
    *
    *
    * @returns {Object} - return document that is traversed.
    *
    * @property {DocObject[]} docData - this is contained JavaScript file.
    *
    * @property {AST} ast - this is AST of JavaScript file.
    *
    * @private
    */
   traverseCode(inDirPath, code)
   {
      let ast;

      let actualCode;
      let filePath = 'unknown';
      let message;

      if (typeof code === 'string') { actualCode = code; }

      // A code instance can be an object with an optional message to be displayed with any error.
      if (typeof code === 'object')
      {
         if (typeof code.code === 'string') { actualCode = code.code; }
         if (typeof code.message === 'string') { message = code.message; }
         if (typeof code.filePath === 'string') { filePath = code.filePath; }
      }

      try
      {
         ast = this._eventbus.triggerSync('tjsdoc:parse:code', actualCode);
      }
      catch (parserError)
      {
         this._eventbus.trigger('tjsdoc:add:invalid:code', { code: actualCode, filePath, message, parserError });

         return null;
      }

      const factory = this._eventbus.triggerSync('tjsdoc:create:code:doc:factory', ast, actualCode, inDirPath,
       filePath);

      this._eventbus.trigger('ast:walker:traverse', ast,
      {
         enterNode: (node, parent) =>
         {
            try
            {
               factory.push(node, parent);
            }
            catch (fatalError)
            {
               this._eventbus.trigger('tjsdoc:add:invalid:code', { code: actualCode, message, node, fatalError });
            }
         }
      });

      return { docData: factory.docData, ast };
   }

   /**
    * Traverse doc comments in given file.
    *
    * @param {string}      inDirPath - root directory path.
    *
    * @param {string}      filePath - target JavaScript file path.
    *
    * @param {string}      [packageName] - npm package name of target.
    *
    * @param {string}      [mainFilePath] - npm main file path of target.
    *
    *
    * @returns {Object} - return document that is traversed.
    *
    * @property {DocObject[]} docData - this is contained JavaScript file.
    *
    * @property {AST} ast - this is AST of JavaScript file.
    *
    * @private
    */
   traverseFile(inDirPath, filePath, packageName, mainFilePath)
   {
      let ast;

      try
      {
         ast = this._eventbus.triggerSync('tjsdoc:parse:file', filePath);
      }
      catch (parserError)
      {
         this._eventbus.trigger('tjsdoc:add:invalid:code', { filePath, parserError });

         return null;
      }

      const factory = this._eventbus.triggerSync('tjsdoc:create:file:doc:factory', ast, inDirPath, filePath,
       packageName, mainFilePath);

      this._eventbus.trigger('ast:walker:traverse', ast,
      {
         enterNode: (node, parent) =>
         {
            try
            {
               factory.push(node, parent);
            }
            catch (fatalError)
            {
               this._eventbus.trigger('tjsdoc:add:invalid:code', { filePath, node, fatalError });
            }
         }
      });

      return { docData: factory.docData, ast };
   }

   /**
    * Traverse doc comments in test code files.
    *
    * @param {string}      type - test code type.
    *
    * @param {string}      inDirPath - root directory path.
    *
    * @param {string}      filePath - target test code file path.
    *
    *
    * @returns {Object} return document info that is traversed.
    *
    * @property {DocObject[]} docData - this is contained test code.
    *
    * @property {AST} ast - this is AST of test code.
    *
    * @private
    */
   traverseTest(type, inDirPath, filePath)
   {
      let ast;

      try
      {
         ast = this._eventbus.triggerSync('tjsdoc:parse:file', filePath);
      }
      catch (parserError)
      {
         this._eventbus.trigger('tjsdoc:add:invalid:code', { filePath, parserError });

         return null;
      }

      const factory = this._eventbus.triggerSync('tjsdoc:create:test:doc:factory', type, ast, inDirPath, filePath);

      this._eventbus.trigger('ast:walker:traverse', ast,
      {
         enterNode: (node, parent) =>
         {
            try
            {
               factory.push(node, parent);
            }
            catch (fatalError)
            {
               this._eventbus.trigger('tjsdoc:add:invalid:code', { filePath, node, fatalError });
            }
         }
      });

      return { docData: factory.docData, ast };
   }
}
<commit_msg>Added `logErrors` parameter to traverse methods allowing logging or errors to be thrown.<commit_after>/**
 * Provides utilities to traverse in memory code and files for main and test parsing generating DocObject and AST data.
 */
export default class TraverseUtil
{
   /**
    * Provides eventbus bindings for traversing in memory code and files for main and test parsing generating DocObject
    * and AST data.
    *
    * @param {PluginEvent}    ev - The plugin eventbus proxy.
    */
   onPluginLoad(ev)
   {
      /**
       * Stores the plugin eventbus proxy.
       * @type {EventProxy}
       */
      this._eventbus = ev.eventbus;

      this._eventbus.on('tjsdoc:traverse:code', this.traverseCode, this);
      this._eventbus.on('tjsdoc:traverse:file', this.traverseFile, this);
      this._eventbus.on('tjsdoc:traverse:test', this.traverseTest, this);
   }

   /**
    * Traverse doc comments in given code.
    *
    * @param {string}         inDirPath - root directory path.
    *
    * @param {object|string}  code - target JavaScript code.
    *
    * @param {boolean}        [logErrors=true] - When true errors are silently logged with InvalidCodeLogger. When false
    *                                            parsing errors are thrown which is useful when watching files.
    *
    * @returns {Object} - return document that is traversed.
    *
    * @property {DocObject[]} docData - this is contained JavaScript file.
    *
    * @property {AST} ast - this is AST of JavaScript file.
    *
    * @private
    */
   traverseCode(inDirPath, code, logErrors = true)
   {
      let ast;

      let actualCode;
      let filePath = 'unknown';
      let message;

      if (typeof code === 'string') { actualCode = code; }

      // A code instance can be an object with an optional message to be displayed with any error.
      if (typeof code === 'object')
      {
         if (typeof code.code === 'string') { actualCode = code.code; }
         if (typeof code.message === 'string') { message = code.message; }
         if (typeof code.filePath === 'string') { filePath = code.filePath; }
      }

      try
      {
         ast = this._eventbus.triggerSync('tjsdoc:parse:code', actualCode);
      }
      catch (parserError)
      {
         if (logErrors)
         {
            this._eventbus.trigger('tjsdoc:add:invalid:code', { code: actualCode, filePath, message, parserError });
            return void 0;
         }
         else
         {
            throw parserError;
         }
      }

      const factory = this._eventbus.triggerSync('tjsdoc:create:code:doc:factory', ast, actualCode, inDirPath,
       filePath);

      this._eventbus.trigger('ast:walker:traverse', ast,
      {
         enterNode: (node, parent) =>
         {
            try
            {
               factory.push(node, parent);
            }
            catch (fatalError)
            {
               if (logErrors)
               {
                  this._eventbus.trigger('tjsdoc:add:invalid:code', { code: actualCode, message, node, fatalError });
               }
               else
               {
                  throw fatalError;
               }
            }
         }
      });

      return { docData: factory.docData, ast };
   }

   /**
    * Traverse doc comments in given file.
    *
    * @param {string}   inDirPath - root directory path.
    *
    * @param {string}   filePath - target JavaScript file path.
    *
    * @param {string}   [packageName] - npm package name of target.
    *
    * @param {string}   [mainFilePath] - npm main file path of target.
    *
    * @param {boolean}  [logErrors=true] - When true errors are silently logged with InvalidCodeLogger. When false
    *                                      parsing errors are thrown which is useful when watching files.
    *
    * @returns {Object} - return document that is traversed.
    *
    * @property {DocObject[]} docData - this is contained JavaScript file.
    *
    * @property {AST} ast - this is AST of JavaScript file.
    *
    * @private
    */
   traverseFile(inDirPath, filePath, packageName, mainFilePath, logErrors = true)
   {
      let ast;

      try
      {
         ast = this._eventbus.triggerSync('tjsdoc:parse:file', filePath);
      }
      catch (parserError)
      {
         if (logErrors)
         {
            this._eventbus.trigger('tjsdoc:add:invalid:code', { filePath, parserError });
            return void 0;
         }
         else
         {
            throw parserError;
         }
      }

      const factory = this._eventbus.triggerSync('tjsdoc:create:file:doc:factory', ast, inDirPath, filePath,
       packageName, mainFilePath);

      this._eventbus.trigger('ast:walker:traverse', ast,
      {
         enterNode: (node, parent) =>
         {
            try
            {
               factory.push(node, parent);
            }
            catch (fatalError)
            {
               if (logErrors)
               {
                  this._eventbus.trigger('tjsdoc:add:invalid:code', { filePath, node, fatalError });
               }
               else
               {
                  throw fatalError;
               }
            }
         }
      });

      return { docData: factory.docData, ast };
   }

   /**
    * Traverse doc comments in test code files.
    *
    * @param {string}   type - test code type.
    *
    * @param {string}   inDirPath - root directory path.
    *
    * @param {string}   filePath - target test code file path.
    *
    * @param {boolean}  [logErrors=true] - When true errors are silently logged with InvalidCodeLogger. When false
    *                                      parsing errors are thrown which is useful when watching files.
    *
    * @returns {Object} return document info that is traversed.
    *
    * @property {DocObject[]} docData - this is contained test code.
    *
    * @property {AST} ast - this is AST of test code.
    *
    * @private
    */
   traverseTest(type, inDirPath, filePath, logErrors = true)
   {
      let ast;

      try
      {
         ast = this._eventbus.triggerSync('tjsdoc:parse:file', filePath);
      }
      catch (parserError)
      {
         if (logErrors)
         {
            this._eventbus.trigger('tjsdoc:add:invalid:code', { filePath, parserError });
            return void 0;
         }
         else
         {
            throw parserError;
         }
      }

      const factory = this._eventbus.triggerSync('tjsdoc:create:test:doc:factory', type, ast, inDirPath, filePath);

      this._eventbus.trigger('ast:walker:traverse', ast,
      {
         enterNode: (node, parent) =>
         {
            try
            {
               factory.push(node, parent);
            }
            catch (fatalError)
            {
               if (logErrors)
               {
                  this._eventbus.trigger('tjsdoc:add:invalid:code', { filePath, node, fatalError });
               }
               else
               {
                  throw fatalError;
               }
            }
         }
      });

      return { docData: factory.docData, ast };
   }
}
<|endoftext|>


<commit_before>var IDBStore;

(function(){

var defaults = {
	dbName: 'IDB',
	storeName: 'Store',
	dbVersion: '1.0',
	keyPath: 'id',
	autoIncrement: true,
	onStoreReady: function(){}
};

IDBStore = function(kwArgs, onStoreReady){
	mixin(this, defaults);
	mixin(this, kwArgs);
	onStoreReady && (this.onStoreReady = onStoreReady);
	this.idb = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
	this.consts = window.IDBTransaction || window.webkitIDBTransaction || window.msIndexedDB;
	this.openDB();
};

IDBStore.prototype = {

	db: null,

	dbName: null,

	dbDescription: null,

	dbVersion: null,

	store: null,

	storeName: null,

	keyPath: null,

	autoIncrement: null,

	onStoreReady: null,

	openDB: function(){
		var openRequest = this.idb.open(this.dbName, this.dbDescription);
		openRequest.onerror = function(error){ console.error('Could not open database.', error); };
		openRequest.onsuccess = hitch(this, function(event){
			this.db = event.target.result;
			this.checkVersion(hitch(this, function(){
				this.getObjectStore(hitch(this, function(){
					this.testFeatures(this.onStoreReady)
				}));
			}));
		});
	},

	testFeatures: function(callback){
		var features = this.features = {};

		// In Chrome, there's no getAll method (getAll is not part of the spec, but handy)
		var getAllTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
		features.hasGetAll = !!getAllTransaction.objectStore(this.storeName).getAll;
		getAllTransaction.abort();

		// In FF, autoIncrement doesn't work.
		// We won't test for that, as testing
		// sometimes fails in Chrome (it's a long
		// story).
	},

	/* version */

	checkVersion: function(onSuccess, onError){
		if(this.getVersion() != this.dbVersion){
			this.setVersion(onSuccess, onError);
		}else{
			onSuccess && onSuccess();
		}
	},

	getVersion: function(){
		return this.db.version;
	},

	setVersion: function(onSuccess, onError){
		onError || (onError = function(error){ console.error('Failed to set version.', error); });
		var versionRequest = this.db.setVersion(this.dbVersion);
		versionRequest.onerror = onError;
		versionRequest.onblocked = onError;
		versionRequest.onsuccess = onSuccess;
	},

	/* object store handling */

	getObjectStore: function(onSuccess, onError){
		if(this.hasObjectStore()){
			this.openExistingObjectStore(onSuccess, onError);
		}else{
			this.createNewObjectStore(onSuccess, onError);
		}
	},

	hasObjectStore: function(){
		return this.db.objectStoreNames.contains(this.storeName);
	},

	createNewObjectStore: function(onSuccess, onError){
		console.log('Creating new objectStore:', this.storeName);
		this.setVersion(hitch(this, function(){
			this.store = this.db.createObjectStore(this.storeName, { keyPath: this.keyPath, autoIncrement: this.autoIncrement});
			console.log('Store ready:', this.store);
			onSuccess && onSuccess(this.store);
		}), onError);
	},

	openExistingObjectStore: function(onSuccess, onError){
		// Having a ref to the store in some transaction state
		// is not useful; do we really want to offer this method?
		console.log('Opening existing objectStore:', this.storeName);
		var emptyTransaction = this.db.transaction([], this.consts.READ_ONLY, 0);
		this.store = emptyTransaction.objectStore(this.storeName);
		console.log('Store ready:', this.store);
		emptyTransaction.abort();
		onSuccess && onSuccess(this.store);
	},

	deleteObjectStore: function(onSuccess, onError){
		onError || (onError = function(error){ console.error('Failed to delete objectStore.', error); });
		console.log('Deleting objectStore:', this.storeName);
		this.setVersion(hitch(this, function(){
			this.db.deleteObjectStore(this.storeName);
			var success = !this.hasObjectStore();
			onSuccess && success && onSuccess();
			onError && !success && onError();
		}), onError);
	},

	/* data manipulation */

	put: function(dataObj, onSuccess, onError){
		// TODO: Check for missing keyPath property.
		onError || (onError = function(error) { console.error('Could not write data.', error); });
		onSuccess || (onSuccess = noop);
		var putTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE, 1000);
		var putRequest = putTransaction.objectStore(this.storeName).put(dataObj);
		putRequest.onsuccess = function(event){ onSuccess(event.target.result); };
		putRequest.onerror = onError;
	},

	get: function(key, onSuccess, onError){
		onError || (onError = function(error) { console.error('Could not read data.', error); });
		onSuccess || (onSuccess = noop);
		var getTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY, 1000);
		var getRequest = getTransaction.objectStore(this.storeName).get(key);
		getRequest.onsuccess = function(event){ onSuccess(event.target.result); };
		getRequest.onerror = onError;
	},

	remove: function(key, onSuccess, onError){
		onError || (onError = function(error) { console.error('Could not remove data.', error); });
		onSuccess || (onSuccess = noop);
		var removeTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE);
		var deleteRequest = removeTransaction.objectStore(this.storeName).delete(key);
		deleteRequest.onsuccess = function(event){ onSuccess(event.target.result); };
		deleteRequest.onerror = onError;
	},

	getAll: function(onSuccess, onError){
		// TODO: Implement cursor using variant for chrome.
		onError || (onError = function(error) { console.error('Could not read data.', error); });
		onSuccess || (onSuccess = noop);
		var getAllTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
		var getAllRequest = getAllTransaction.objectStore(this.storeName).getAll();
		getAllRequest.onsuccess = function(event){ onSuccess(event.target.result); };
		getAllRequest.onerror = onError;
	},

	clear: function(onSuccess, onError){
		onError || (onError = function(error) { console.error('Could not clear store.', error); });
		onSuccess || (onSuccess = noop);
		var clearTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE);
		var clearRequest = clearTransaction.objectStore(this.storeName).clear();
		clearRequest.onsuccess = function(event){ onSuccess(event.target.result); };
		clearRequest.onerror = onError;
	},

	/* indexing */
	// TODO: implement

	/* key ranges / cursors */
	// TODO: implement
};

/** helpers **/

var noop = function(){};
var empty = {};
var mixin = function(target, source){
	var name, s;
	for(name in source){
		s = source[name];
		if(s !== empty[name] && s !== target[name]){
			target[name] = s;
		}
	}
	return target;
};
var hitch = function (scope, method){
	if(!method){ method = scope; scope = null; }
	if(typeof method == "string"){
		scope = scope || window;
		if(!scope[method]){ throw(['method not found']); }
		return function(){ return scope[method].apply(scope, arguments || []); };
	}
	return !scope ? method : function(){ return method.apply(scope, arguments || []); };
};

})();<commit_msg>Adding cursor variant of getAll for Chrome.<commit_after>var IDBStore;

(function(){

var defaults = {
	dbName: 'IDB',
	storeName: 'Store',
	dbVersion: '1.0',
	keyPath: 'id',
	autoIncrement: true,
	onStoreReady: function(){}
};

IDBStore = function(kwArgs, onStoreReady){
	mixin(this, defaults);
	mixin(this, kwArgs);
	onStoreReady && (this.onStoreReady = onStoreReady);
	this.idb = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
	this.consts = window.IDBTransaction || window.webkitIDBTransaction || window.msIndexedDB;
	this.openDB();
};

IDBStore.prototype = {

	db: null,

	dbName: null,

	dbDescription: null,

	dbVersion: null,

	store: null,

	storeName: null,

	keyPath: null,

	autoIncrement: null,

	onStoreReady: null,

	openDB: function(){
		var openRequest = this.idb.open(this.dbName, this.dbDescription);
		openRequest.onerror = function(error){ console.error('Could not open database.', error); };
		openRequest.onsuccess = hitch(this, function(event){
			this.db = event.target.result;
			this.checkVersion(hitch(this, function(){
				this.getObjectStore(hitch(this, function(){
					this.testFeatures(this.onStoreReady)
				}));
			}));
		});
	},

	testFeatures: function(callback){
		var features = this.features = {};

		// In Chrome, there's no getAll method (getAll is not part of the spec, but handy)
		var getAllTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
		features.hasGetAll = !!getAllTransaction.objectStore(this.storeName).getAll;
		getAllTransaction.abort();

		// In FF, autoIncrement doesn't work.
		// We won't test for that, as testing
		// sometimes fails in Chrome (it's a long
		// story).

		callback && callback();
	},

	/* version */

	checkVersion: function(onSuccess, onError){
		if(this.getVersion() != this.dbVersion){
			this.setVersion(onSuccess, onError);
		}else{
			onSuccess && onSuccess();
		}
	},

	getVersion: function(){
		return this.db.version;
	},

	setVersion: function(onSuccess, onError){
		onError || (onError = function(error){ console.error('Failed to set version.', error); });
		var versionRequest = this.db.setVersion(this.dbVersion);
		versionRequest.onerror = onError;
		versionRequest.onblocked = onError;
		versionRequest.onsuccess = onSuccess;
	},

	/* object store handling */

	getObjectStore: function(onSuccess, onError){
		if(this.hasObjectStore()){
			this.openExistingObjectStore(onSuccess, onError);
		}else{
			this.createNewObjectStore(onSuccess, onError);
		}
	},

	hasObjectStore: function(){
		return this.db.objectStoreNames.contains(this.storeName);
	},

	createNewObjectStore: function(onSuccess, onError){
		console.log('Creating new objectStore:', this.storeName);
		this.setVersion(hitch(this, function(){
			this.store = this.db.createObjectStore(this.storeName, { keyPath: this.keyPath, autoIncrement: this.autoIncrement});
			console.log('Store ready:', this.store);
			onSuccess && onSuccess(this.store);
		}), onError);
	},

	openExistingObjectStore: function(onSuccess, onError){
		// Having a ref to the store in some transaction state
		// is not useful; do we really want to offer this method?
		console.log('Opening existing objectStore:', this.storeName);
		var emptyTransaction = this.db.transaction([], this.consts.READ_ONLY, 0);
		this.store = emptyTransaction.objectStore(this.storeName);
		console.log('Store ready:', this.store);
		onSuccess && onSuccess(this.store);
	},

	deleteObjectStore: function(onSuccess, onError){
		onError || (onError = function(error){ console.error('Failed to delete objectStore.', error); });
		console.log('Deleting objectStore:', this.storeName);
		this.setVersion(hitch(this, function(){
			this.db.deleteObjectStore(this.storeName);
			var success = !this.hasObjectStore();
			onSuccess && success && onSuccess();
			onError && !success && onError();
		}), onError);
	},

	/* data manipulation */

	put: function(dataObj, onSuccess, onError){
		// TODO: Check for missing keyPath property.
		onError || (onError = function(error) { console.error('Could not write data.', error); });
		onSuccess || (onSuccess = noop);
		var putTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE, 1000);
		var putRequest = putTransaction.objectStore(this.storeName).put(dataObj);
		putRequest.onsuccess = function(event){ onSuccess(event.target.result); };
		putRequest.onerror = onError;
	},

	get: function(key, onSuccess, onError){
		onError || (onError = function(error) { console.error('Could not read data.', error); });
		onSuccess || (onSuccess = noop);
		var getTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY, 1000);
		var getRequest = getTransaction.objectStore(this.storeName).get(key);
		getRequest.onsuccess = function(event){ onSuccess(event.target.result); };
		getRequest.onerror = onError;
	},

	remove: function(key, onSuccess, onError){
		onError || (onError = function(error) { console.error('Could not remove data.', error); });
		onSuccess || (onSuccess = noop);
		var removeTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE);
		var deleteRequest = removeTransaction.objectStore(this.storeName).delete(key);
		deleteRequest.onsuccess = function(event){ onSuccess(event.target.result); };
		deleteRequest.onerror = onError;
	},

	getAll: function(onSuccess, onError){
		onError || (onError = function(error) { console.error('Could not read data.', error); });
		onSuccess || (onSuccess = noop);
		var getAllTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
		if(this.features.hasGetAll){
			var getAllRequest = getAllTransaction.objectStore(this.storeName).getAll();
			getAllRequest.onsuccess = function(event){ onSuccess(event.target.result); };
			getAllRequest.onerror = onError;
		}else{
			this._getAllCursor(getAllTransaction, onSuccess, onError);
		}
	},

	_getAllCursor: function(tr, onSuccess, onError){
		var all = [];
		var store = tr.objectStore(this.storeName);
		var cursorRequest = store.openCursor();

		cursorRequest.onsuccess = function(event) {
          var cursor = event.target.result;
          if (cursor) {
               all.push(cursor.value);
               cursor.continue();
          }
          else {
              onSuccess(all);
          }
        };
        cursorRequest.onError = onError;
	},

	clear: function(onSuccess, onError){
		onError || (onError = function(error) { console.error('Could not clear store.', error); });
		onSuccess || (onSuccess = noop);
		var clearTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE);
		var clearRequest = clearTransaction.objectStore(this.storeName).clear();
		clearRequest.onsuccess = function(event){ onSuccess(event.target.result); };
		clearRequest.onerror = onError;
	},

	/* indexing */
	// TODO: implement

	/* key ranges / cursors */
	// TODO: implement

};

/** helpers **/

var noop = function(){};
var empty = {};
var mixin = function(target, source){
	var name, s;
	for(name in source){
		s = source[name];
		if(s !== empty[name] && s !== target[name]){
			target[name] = s;
		}
	}
	return target;
};
var hitch = function (scope, method){
	if(!method){ method = scope; scope = null; }
	if(typeof method == "string"){
		scope = scope || window;
		if(!scope[method]){ throw(['method not found']); }
		return function(){ return scope[method].apply(scope, arguments || []); };
	}
	return !scope ? method : function(){ return method.apply(scope, arguments || []); };
};

})();<|endoftext|>