<commit_before>"use strict";

var optionDefaults = {
    type: "Code 128",
    hasChecksum: false,
    x: 0,
    y: 0,
    moduleWidth: 2.892,
    height: 90.72,
    horizontalAlign: "left",
    verticalAlign: "top",
    quietZoneSize: 10,
    angle: 0,
    maxWidth: Infinity,
    width: NaN
};

// Copy default values from source to target if they don't exist.
var copyDefaults = function(target, source) {
    for (var key in source) {
        if (typeof target[key] === "undefined") {
            target[key] = source[key];
        }
    }
};

/**
 * @summary Draw a barcode to a canvas graphics context.
 * @todo IMB, Pharmacode, PostBar, POSTNET, Telepen
 * @param {Context2D|String} g An HTML5 or node-canvas graphics context or the output format.  The only supported non-canvas output format is "svg".
 * @param {String} text Barcode text (without start, end, or check characters).
 * @param {Object} options Controls what barcode is drawn, where, and how.
 * @param {String} options.type Barcode type.  Defaults to Code 128.  Other valid options are "Codabar", "Code 39", "EAN-8", "EAN-13", "FIM", "ITF" (interleaved 2 of 5), and "UPC-A".
 * @param {Boolean} options.hasChecksum If true, the barcode already has a checksum (which will be validated); if false, calculate and add a checksum. Defaults to false. **Currently works only for EAN-type barcodes (EAN-8, EAN-13, UPC-A).**
 * @param {Number} options.x Where to draw barcode.  Defaults to 0.
 * @param {Number} options.y Where to draw the barcode.  Defaults to 0.
 * @param {String} options.horizontalAlign How to align the barcode.  Defaults to "left".  Other options are "center" and "right".
 * @param {String} options.verticalAlign How to align the barcode.  Defaults to "top".  Other options are "middle" and "bottom".
 * @param {Number} options.height Barcode height.  Defaults to 90.72.
 * @param {Number} options.moduleWidth Width of thinnest bar.  Defaults to 2.892.
 * @param {Number} options.quietZoneSize Number of moduleWidths in quiet zone on either side.  Defaults to 10.
 * @param {Number} options.angle Rotate barcode this many degrees clockwise.  Defaults to 0.
 * @param {Number} options.maxWidth Maximum barcode width (including quiet zones).  If specified, then the moduleWidth will be adjusted if necessary to make the entire barcode fit in the given width.
 * @param {Number} options.width If given, then ignore moduleWidth and maxWidth and set the moduleWidth so that the barcode will have the given width.
 */
drawBarcode = bardcode.drawBarcode = function(g, text, options) {
    // Validate input.
    if (typeof g !== "object" && g !== "svg") {
        throw new Error("drawBarcode: expected `g' to be an object or 'svg'.");
    }

    if (!text || typeof text !== "string") {
        throw new Error("drawBarcode: expected `text' to be a non-empty string.");
    }

    if (typeof options !== "object") {
        options = { };
    }

    copyDefaults(options, optionDefaults);
    bardcode.validateDrawBarcodeOptions(options);

    var encodeData;
    switch (options.type) {
        case "Codabar":
            encodeData = bardcode.encodeCodabar(text);
            break;
        case "Code 128":
            encodeData = bardcode.encodeCode128(text);
            break;
        case "Code 39":
            encodeData = bardcode.encodeCode39(text);
            break;
        case "ITF":
            encodeData = bardcode.encodeITF(text);
            break;
        case "FIM":
            encodeData = bardcode.encodeFIM(text);
            break;
        case "EAN-8":
        case "EAN-13":
        case "UPC-A":
            var expectedLength;
            if (options.type === "EAN-8") { expectedLength = 7; }
            if (options.type === "EAN-13") { expectedLength = 12; }
            if (options.type === "UPC-A") { expectedLength = 11; }
            if (options.hasChecksum) { expectedLength += 1; }
            if (expectedLength !== text.length) {
                throw new Error(options.type + " must be of length " + expectedLength);
            }
            encodeData = bardcode.encodeEAN(text, options.hasChecksum);
            break;
    }

    switch (encodeData.type) {
        case "bits":
            return bardcode.drawBitsBarcode(g, options, encodeData);
        default:
            throw new Error("Unrecognized encoded barcode type: " + encodeData.type);
    }
};

bardcode.validateDrawBarcodeOptions = function validateDrawBarcodeOptions(options) {
    bardcode.assertIsBoolean(options.hasChecksum, "options.hasChecksum");
    bardcode.assertIsNumber(options.x, "options.x");
    bardcode.assertIsNumber(options.y, "options.y");
    bardcode.assertIsValidHorizontalAlign(options.horizontalAlign);
    bardcode.assertIsValidVerticalAlign(options.verticalAlign);
    bardcode.assertIsPositiveNumber(options.height, "options.height");
    bardcode.assertIsPositiveNumber(options.moduleWidth, "options.moduleWidth");
    bardcode.assertIsNonNegativeNumber(options.quietZoneSize, "options.quietZoneSize");
    bardcode.assertIsNumber(options.angle, "options.angle");
    bardcode.assertIsPositiveNumber(options.maxWidth, "options.maxWidth");

    // width can either be NaN or a positive number.
    if (!isNaN(options.width)) {
        bardcode.assertIsPositiveNumber(options.width, "options.width");
    }
};

bardcode.drawBitsBarcode = function(g, options, encodeData) {
    if (g === "svg") {
        return bardcode.drawBitsBarcodeToSVG(options, encodeData);
    } else {
        return bardcode.drawBitsBarcodeToCanvas(g, options, encodeData);
    }
};

<commit_msg>Allow character arrays as input to drawBarcode.<commit_after>"use strict";

var optionDefaults = {
    type: "Code 128",
    hasChecksum: false,
    x: 0,
    y: 0,
    moduleWidth: 2.892,
    height: 90.72,
    horizontalAlign: "left",
    verticalAlign: "top",
    quietZoneSize: 10,
    angle: 0,
    maxWidth: Infinity,
    width: NaN
};

// Copy default values from source to target if they don't exist.
var copyDefaults = function(target, source) {
    for (var key in source) {
        if (typeof target[key] === "undefined") {
            target[key] = source[key];
        }
    }
};

/**
 * @summary Draw a barcode to a canvas graphics context.
 * @todo IMB, Pharmacode, PostBar, POSTNET, Telepen
 * @param {Context2D|String} g An HTML5 or node-canvas graphics context or the output format.  The only supported non-canvas output format is "svg".
 * @param {String|String[]} text Barcode text (without start, end, or check characters).  It can also be an array of characters, in case you want to include a command character, like "FNC 1".
 * @param {Object} options Controls what barcode is drawn, where, and how.
 * @param {String} options.type Barcode type.  Defaults to Code 128.  Other valid options are "Codabar", "Code 39", "EAN-8", "EAN-13", "FIM", "ITF" (interleaved 2 of 5), and "UPC-A".
 * @param {Boolean} options.hasChecksum If true, the barcode already has a checksum (which will be validated); if false, calculate and add a checksum. Defaults to false. **Currently works only for EAN-type barcodes (EAN-8, EAN-13, UPC-A).**
 * @param {Number} options.x Where to draw barcode.  Defaults to 0.
 * @param {Number} options.y Where to draw the barcode.  Defaults to 0.
 * @param {String} options.horizontalAlign How to align the barcode.  Defaults to "left".  Other options are "center" and "right".
 * @param {String} options.verticalAlign How to align the barcode.  Defaults to "top".  Other options are "middle" and "bottom".
 * @param {Number} options.height Barcode height.  Defaults to 90.72.
 * @param {Number} options.moduleWidth Width of thinnest bar.  Defaults to 2.892.
 * @param {Number} options.quietZoneSize Number of moduleWidths in quiet zone on either side.  Defaults to 10.
 * @param {Number} options.angle Rotate barcode this many degrees clockwise.  Defaults to 0.
 * @param {Number} options.maxWidth Maximum barcode width (including quiet zones).  If specified, then the moduleWidth will be adjusted if necessary to make the entire barcode fit in the given width.
 * @param {Number} options.width If given, then ignore moduleWidth and maxWidth and set the moduleWidth so that the barcode will have the given width.
 */
drawBarcode = bardcode.drawBarcode = function(g, text, options) {
    // Validate input.
    if (typeof g !== "object" && g !== "svg") {
        throw new Error("drawBarcode: expected `g' to be an object or 'svg'.");
    }

    if (!text) {
        throw new Error("drawBarcode: missing required parameter `text'.");
    }

    if (typeof options !== "object") {
        options = { };
    }

    copyDefaults(options, optionDefaults);
    bardcode.validateDrawBarcodeOptions(options);

    var encodeData;
    switch (options.type) {
        case "Codabar":
            encodeData = bardcode.encodeCodabar(text);
            break;
        case "Code 128":
            encodeData = bardcode.encodeCode128(text);
            break;
        case "Code 39":
            encodeData = bardcode.encodeCode39(text);
            break;
        case "ITF":
            encodeData = bardcode.encodeITF(text);
            break;
        case "FIM":
            encodeData = bardcode.encodeFIM(text);
            break;
        case "EAN-8":
        case "EAN-13":
        case "UPC-A":
            var expectedLength;
            if (options.type === "EAN-8") { expectedLength = 7; }
            if (options.type === "EAN-13") { expectedLength = 12; }
            if (options.type === "UPC-A") { expectedLength = 11; }
            if (options.hasChecksum) { expectedLength += 1; }
            if (expectedLength !== text.length) {
                throw new Error(options.type + " must be of length " + expectedLength);
            }
            encodeData = bardcode.encodeEAN(text, options.hasChecksum);
            break;
    }

    switch (encodeData.type) {
        case "bits":
            return bardcode.drawBitsBarcode(g, options, encodeData);
        default:
            throw new Error("Unrecognized encoded barcode type: " + encodeData.type);
    }
};

bardcode.validateDrawBarcodeOptions = function validateDrawBarcodeOptions(options) {
    bardcode.assertIsBoolean(options.hasChecksum, "options.hasChecksum");
    bardcode.assertIsNumber(options.x, "options.x");
    bardcode.assertIsNumber(options.y, "options.y");
    bardcode.assertIsValidHorizontalAlign(options.horizontalAlign);
    bardcode.assertIsValidVerticalAlign(options.verticalAlign);
    bardcode.assertIsPositiveNumber(options.height, "options.height");
    bardcode.assertIsPositiveNumber(options.moduleWidth, "options.moduleWidth");
    bardcode.assertIsNonNegativeNumber(options.quietZoneSize, "options.quietZoneSize");
    bardcode.assertIsNumber(options.angle, "options.angle");
    bardcode.assertIsPositiveNumber(options.maxWidth, "options.maxWidth");

    // width can either be NaN or a positive number.
    if (!isNaN(options.width)) {
        bardcode.assertIsPositiveNumber(options.width, "options.width");
    }
};

bardcode.drawBitsBarcode = function(g, options, encodeData) {
    if (g === "svg") {
        return bardcode.drawBitsBarcodeToSVG(options, encodeData);
    } else {
        return bardcode.drawBitsBarcodeToCanvas(g, options, encodeData);
    }
};

<|endoftext|>


<commit_before>/**
 * This file provided by Facebook is for non-commercial testing and evaluation
 * purposes only. Facebook reserves all rights not expressly granted.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

var fs = require('fs');
var path = require('path');
var express = require('express');
var bodyParser = require('body-parser');
var app = express();

var COMMENTS_FILE = path.join(__dirname, 'comments.json');

app.set('port', (process.env.PORT || 3000));

app.use('/', express.static(path.join(__dirname, 'public')));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
app.use(function(req, res, next) {
    //set permissive CORS header
    res.setHeader('Access-Control-Allow-Origin', '*');
    next();
});

app.get('/api/comments', function(req, res) {
  fs.readFile(COMMENTS_FILE, function(err, data) {
    if (err) {
      console.error(err);
      process.exit(1);
    }
    res.setHeader('Cache-Control', 'no-cache');
    res.json(JSON.parse(data));
  });
});

app.post('/api/comments', function(req, res) {
  fs.readFile(COMMENTS_FILE, function(err, data) {
    if (err) {
      console.error(err);
      process.exit(1);
    }
    var comments = JSON.parse(data);
    // NOTE: In a real implementation, we would likely rely on a database or
    // some other approach (e.g. UUIDs) to ensure a globally unique id. We'll
    // treat Date.now() as unique-enough for our purposes.
    var newComment = {
      id: Date.now(),
      author: req.body.author,
      text: req.body.text,
    };
    comments.push(newComment);
    fs.writeFile(COMMENTS_FILE, JSON.stringify(comments, null, 4), function(err) {
      if (err) {
        console.error(err);
        process.exit(1);
      }
      res.setHeader('Cache-Control', 'no-cache');
      res.json(comments);
    });
  });
});

app.listen(app.get('port'), function() {
  console.log('Server started: http://localhost:' + app.get('port') + '/');
});
<commit_msg>Clean up some code in the JS server, add comments<commit_after>/**
 * This file provided by Facebook is for non-commercial testing and evaluation
 * purposes only. Facebook reserves all rights not expressly granted.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

var fs = require('fs');
var path = require('path');
var express = require('express');
var bodyParser = require('body-parser');
var app = express();

var COMMENTS_FILE = path.join(__dirname, 'comments.json');

app.set('port', (process.env.PORT || 3000));

app.use('/', express.static(path.join(__dirname, 'public')));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));

// Additional middleware which will set headers that we need on each request.
app.use(function(req, res, next) {
    // Set permissive CORS header - this allows this server to be used only as
    // an API server in conjunction with something like webpack-dev-server.
    res.setHeader('Access-Control-Allow-Origin', '*');

    // Disable caching so we'll always get the latest comments.
    res.setHeader('Cache-Control', 'no-cache');
    next();
});

app.get('/api/comments', function(req, res) {
  fs.readFile(COMMENTS_FILE, function(err, data) {
    if (err) {
      console.error(err);
      process.exit(1);
    }
    res.json(JSON.parse(data));
  });
});

app.post('/api/comments', function(req, res) {
  fs.readFile(COMMENTS_FILE, function(err, data) {
    if (err) {
      console.error(err);
      process.exit(1);
    }
    var comments = JSON.parse(data);
    // NOTE: In a real implementation, we would likely rely on a database or
    // some other approach (e.g. UUIDs) to ensure a globally unique id. We'll
    // treat Date.now() as unique-enough for our purposes.
    var newComment = {
      id: Date.now(),
      author: req.body.author,
      text: req.body.text,
    };
    comments.push(newComment);
    fs.writeFile(COMMENTS_FILE, JSON.stringify(comments, null, 4), function(err) {
      if (err) {
        console.error(err);
        process.exit(1);
      }
      res.json(comments);
    });
  });
});

app.listen(app.get('port'), function() {
  console.log('Server started: http://localhost:' + app.get('port') + '/');
});
<|endoftext|>


<commit_before>var Changeset = require('./Changeset');
var ChangesetUtils = require('./ChangesetUtils');
var _ = require('./underscore');

var lineMarkerAttribute = 'lmkr';

// If one of these attributes are set to the first character of a
// line it is considered as a line attribute marker i.e. attributes
// set on this marker are applied to the whole line.
// The list attribute is only maintained for compatibility reasons
var lineAttributes = [lineMarkerAttribute,'list'];

/*
  The Attribute manager builds changesets based on a document
  representation for setting and removing range or line-based attributes.

  @param rep the document representation to be used
  @param applyChangesetCallback this callback will be called
    once a changeset has been built.

  A document representation contains
  - an array `alines` containing 1 attributes string for each line
  - an Attribute pool `apool`
  - a SkipList `lines` containing the text lines of the document.
*/

var AttributeManager = function(rep, applyChangesetCallback)
{
  this.rep = rep;
  this.applyChangesetCallback = applyChangesetCallback;
  this.author = '';

  // If the first char in a line has one of the following attributes
  // it will be considered as a line marker
};

AttributeManager.lineAttributes = lineAttributes;

AttributeManager.prototype = _(AttributeManager.prototype).extend({

  applyChangeset: function(changeset){
    if(!this.applyChangesetCallback) return changeset;

    var cs = changeset.toString();
    if (!Changeset.isIdentity(cs))
    {
      this.applyChangesetCallback(cs);
    }

    return changeset;
  },

  /*
    Sets attributes on a range
    @param start [row, col] tuple pointing to the start of the range
    @param end [row, col] tuple pointing to the end of the range
    @param attribute: an array of attributes
  */
  setAttributesOnRange: function(start, end, attribs)
  {
    var builder = Changeset.builder(this.rep.lines.totalWidth());
    ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, start);
    ChangesetUtils.buildKeepRange(this.rep, builder, start, end, attribs, this.rep.apool);
    return this.applyChangeset(builder);
  },

  /*
    Returns if the line already has a line marker
    @param lineNum: the number of the line
  */
  lineHasMarker: function(lineNum){
    var that = this;

    return _.find(lineAttributes, function(attribute){
      return that.getAttributeOnLine(lineNum, attribute) != '';
    }) !== undefined;
  },

  /*
    Gets a specified attribute on a line
    @param lineNum: the number of the line to set the attribute for
    @param attributeKey: the name of the attribute to get, e.g. list
  */
  getAttributeOnLine: function(lineNum, attributeName){
    // get  `attributeName` attribute of first char of line
    var aline = this.rep.alines[lineNum];
    if (aline)
    {
      var opIter = Changeset.opIterator(aline);
      if (opIter.hasNext())
      {
        return Changeset.opAttributeValue(opIter.next(), attributeName, this.rep.apool) || '';
      }
    }
    return '';
  },

  /*
    Gets all attributes on a line
    @param lineNum: the number of the line to set the attribute for
  */
  getAttributesOnLine: function(lineNum){
    // get attributes of first char of line
    var aline = this.rep.alines[lineNum];
    var attributes = []
    if (aline)
    {
      var opIter = Changeset.opIterator(aline)
        , op
      if (opIter.hasNext())
      {
        op = opIter.next()
        if(!op.attribs) return []

        Changeset.eachAttribNumber(op.attribs, function(n) {
          attributes.push([this.rep.apool.getAttribKey(n), this.rep.apool.getAttribValue(n)])
        }.bind(this))
        return attributes;
      }
    }
    return [];
  },

  /*
    Sets a specified attribute on a line
    @param lineNum: the number of the line to set the attribute for
    @param attributeKey: the name of the attribute to set, e.g. list
    @param attributeValue: an optional parameter to pass to the attribute (e.g. indention level)

  */
  setAttributeOnLine: function(lineNum, attributeName, attributeValue){
    var loc = [0,0];
    var builder = Changeset.builder(this.rep.lines.totalWidth());
    var hasMarker = this.lineHasMarker(lineNum);

    ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 0]));

    if(hasMarker){
      ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 1]), [
        [attributeName, attributeValue]
      ], this.rep.apool);
    }else{
        // add a line marker
        builder.insert('*', [
          ['author', this.author],
          ['insertorder', 'first'],
          [lineMarkerAttribute, '1'],
          [attributeName, attributeValue]
        ], this.rep.apool);
    }

    return this.applyChangeset(builder);
  },

  /*
     Removes a specified attribute on a line
     @param lineNum: the number of the affected line
     @param attributeKey: the name of the attribute to remove, e.g. list

   */
   removeAttributeOnLine: function(lineNum, attributeName, attributeValue){
     var loc = [0,0];
     var builder = Changeset.builder(this.rep.lines.totalWidth());
     var hasMarker = this.lineHasMarker(lineNum);
     var attribs

     attribs = this.getAttributesOnLine(lineNum).map(function(attrib) {
       if(attrib[0] === attributeName) return [attributeName, null]
       return attrib
     })

     if(hasMarker){
       ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 0]));
       ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 1]), attribs, this.rep.apool);
     }

     return this.applyChangeset(builder);
   },

   /*
     Sets a specified attribute on a line
     @param lineNum: the number of the line to set the attribute for
     @param attributeKey: the name of the attribute to set, e.g. list
     @param attributeValue: an optional parameter to pass to the attribute (e.g. indention level)
  */
  toggleAttributeOnLine: function(lineNum, attributeName, attributeValue) {
    return this.getAttributeOnLine(attributeName) ?
      this.removeAttributeOnLine(lineNum, attributeName) :
      this.setAttributeOnLine(lineNum, attributeName, attributeValue);

  }
});

module.exports = AttributeManager;<commit_msg>AttribManager#removeAttribOnLine: Remove Linemarker entirely if there's nothing else left<commit_after>var Changeset = require('./Changeset');
var ChangesetUtils = require('./ChangesetUtils');
var _ = require('./underscore');

var lineMarkerAttribute = 'lmkr';

// If one of these attributes are set to the first character of a
// line it is considered as a line attribute marker i.e. attributes
// set on this marker are applied to the whole line.
// The list attribute is only maintained for compatibility reasons
var lineAttributes = [lineMarkerAttribute,'list'];

/*
  The Attribute manager builds changesets based on a document
  representation for setting and removing range or line-based attributes.

  @param rep the document representation to be used
  @param applyChangesetCallback this callback will be called
    once a changeset has been built.

  A document representation contains
  - an array `alines` containing 1 attributes string for each line
  - an Attribute pool `apool`
  - a SkipList `lines` containing the text lines of the document.
*/

var AttributeManager = function(rep, applyChangesetCallback)
{
  this.rep = rep;
  this.applyChangesetCallback = applyChangesetCallback;
  this.author = '';

  // If the first char in a line has one of the following attributes
  // it will be considered as a line marker
};

AttributeManager.lineAttributes = lineAttributes;

AttributeManager.prototype = _(AttributeManager.prototype).extend({

  applyChangeset: function(changeset){
    if(!this.applyChangesetCallback) return changeset;

    var cs = changeset.toString();
    if (!Changeset.isIdentity(cs))
    {
      this.applyChangesetCallback(cs);
    }

    return changeset;
  },

  /*
    Sets attributes on a range
    @param start [row, col] tuple pointing to the start of the range
    @param end [row, col] tuple pointing to the end of the range
    @param attribute: an array of attributes
  */
  setAttributesOnRange: function(start, end, attribs)
  {
    var builder = Changeset.builder(this.rep.lines.totalWidth());
    ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, start);
    ChangesetUtils.buildKeepRange(this.rep, builder, start, end, attribs, this.rep.apool);
    return this.applyChangeset(builder);
  },

  /*
    Returns if the line already has a line marker
    @param lineNum: the number of the line
  */
  lineHasMarker: function(lineNum){
    var that = this;

    return _.find(lineAttributes, function(attribute){
      return that.getAttributeOnLine(lineNum, attribute) != '';
    }) !== undefined;
  },

  /*
    Gets a specified attribute on a line
    @param lineNum: the number of the line to set the attribute for
    @param attributeKey: the name of the attribute to get, e.g. list
  */
  getAttributeOnLine: function(lineNum, attributeName){
    // get  `attributeName` attribute of first char of line
    var aline = this.rep.alines[lineNum];
    if (aline)
    {
      var opIter = Changeset.opIterator(aline);
      if (opIter.hasNext())
      {
        return Changeset.opAttributeValue(opIter.next(), attributeName, this.rep.apool) || '';
      }
    }
    return '';
  },

  /*
    Gets all attributes on a line
    @param lineNum: the number of the line to set the attribute for
  */
  getAttributesOnLine: function(lineNum){
    // get attributes of first char of line
    var aline = this.rep.alines[lineNum];
    var attributes = []
    if (aline)
    {
      var opIter = Changeset.opIterator(aline)
        , op
      if (opIter.hasNext())
      {
        op = opIter.next()
        if(!op.attribs) return []

        Changeset.eachAttribNumber(op.attribs, function(n) {
          attributes.push([this.rep.apool.getAttribKey(n), this.rep.apool.getAttribValue(n)])
        }.bind(this))
        return attributes;
      }
    }
    return [];
  },

  /*
    Sets a specified attribute on a line
    @param lineNum: the number of the line to set the attribute for
    @param attributeKey: the name of the attribute to set, e.g. list
    @param attributeValue: an optional parameter to pass to the attribute (e.g. indention level)

  */
  setAttributeOnLine: function(lineNum, attributeName, attributeValue){
    var loc = [0,0];
    var builder = Changeset.builder(this.rep.lines.totalWidth());
    var hasMarker = this.lineHasMarker(lineNum);

    ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 0]));

    if(hasMarker){
      ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 1]), [
        [attributeName, attributeValue]
      ], this.rep.apool);
    }else{
        // add a line marker
        builder.insert('*', [
          ['author', this.author],
          ['insertorder', 'first'],
          [lineMarkerAttribute, '1'],
          [attributeName, attributeValue]
        ], this.rep.apool);
    }

    return this.applyChangeset(builder);
  },

  /*
     Removes a specified attribute on a line
     @param lineNum: the number of the affected line
     @param attributeKey: the name of the attribute to remove, e.g. list

   */
   removeAttributeOnLine: function(lineNum, attributeName, attributeValue){
     var loc = [0,0];
     var builder = Changeset.builder(this.rep.lines.totalWidth());
     var hasMarker = this.lineHasMarker(lineNum);
     var attribs

     attribs = this.getAttributesOnLine(lineNum).map(function(attrib) {
       if(attrib[0] === attributeName) return [attributeName, null]
       return attrib
     })

     if(hasMarker){
       ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 0]));
       // If length == 4, there's [author, lmkr, insertorder, + the attrib being removed] thus we can remove the marker entirely
       if(attribs.length == 4) ChangesetUtils.buildRemoveRange(this.rep, builder, loc, (loc = [lineNum, 1]))
       else ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 1]), attribs, this.rep.apool);
     }

     return this.applyChangeset(builder);
   },

   /*
     Sets a specified attribute on a line
     @param lineNum: the number of the line to set the attribute for
     @param attributeKey: the name of the attribute to set, e.g. list
     @param attributeValue: an optional parameter to pass to the attribute (e.g. indention level)
  */
  toggleAttributeOnLine: function(lineNum, attributeName, attributeValue) {
    return this.getAttributeOnLine(attributeName) ?
      this.removeAttributeOnLine(lineNum, attributeName) :
      this.setAttributeOnLine(lineNum, attributeName, attributeValue);

  }
});

module.exports = AttributeManager;<|endoftext|>


<commit_before>(function() {
  "use strict";

  function buildDraggable(Sortable) {
    function removeNode (node) {
      node.parentElement.removeChild(node)
    }

    function insertNodeAt (fatherNode, node, position) {
      if (position < fatherNode.children.length) {
        fatherNode.insertBefore(node, fatherNode.children[position])
      } else {
        fatherNode.appendChild(node)
      }
    }

    function computeVmIndex (vnodes, element) {
      return vnodes.map(elt => elt.elm).indexOf(element)
    }

    function computeIndexes (slots, children) {
      return (!slots)? [] : Array.prototype.map.call(children, elt => computeVmIndex(slots, elt))
    }

    function emit (evtName, evtData) {
      this.$emit( evtName.toLowerCase(), evtData)
    }

    function delegateAndEmit (evtName) {
      return (evtData) => {
        if (this.realList!==null) {
          this['onDrag' + evtName](evtData)
        }
        emit.call(this, evtName, evtData)
      }
    }

    const eventsListened = ['Start', 'Add', 'Remove', 'Update', 'End']
    const eventsToEmit = ['Choose', 'Sort', 'Filter', 'Clone']
    const readonlyProperties = ['Move', ...eventsListened, ...eventsToEmit].map(evt => 'on'+evt)
    var draggingElement = null

    const props = {
      options: Object,
      list: {
        type: Array,
        required: false,
        default: null
      },
      value: {
        type: Array,
        required: false,
        default: null
      },
      clone: {
        type: Function,
        default : (original) => { return original;}
      },
      element: {
        type: String,
        default: 'div'
      },
      move: {
        type: Function,
        default: null
      }
    }

    const draggableComponent = {
      props,

      data() {
        return {
          transitionMode: false
        }
      },

      render (h) {
        if (this.$slots.default && this.$slots.default.length===1) {
          const child = this.$slots.default[0]
          if (child.componentOptions && child.componentOptions.tag==="transition-group") {
            this.transitionMode = true
          }
        }
        return h(this.element, null, this.$slots.default);
      },

      mounted () {
        var optionsAdded = {};
        eventsListened.forEach( elt => {
          optionsAdded['on' + elt] = delegateAndEmit.call(this, elt)
        });

        eventsToEmit.forEach( elt => {
          optionsAdded['on' + elt] = emit.bind(this, elt)
        });

        const options = Object.assign({}, this.options, optionsAdded, { onMove: evt => {return this.onDragMove(evt);} })
        this._sortable = new Sortable(this.rootContainer, options)
        this.computeIndexes()
      },

      beforeDestroy () {
        this._sortable.destroy()
      },

      computed : {
        rootContainer () {
          return this.transitionMode? this.$el.children[0] : this.$el;
        },

        isCloning () {
          return (!!this.options) && (this.options.group !== null) && (this.options.group.pull === 'clone')
        },

        realList () {
          return (!!this.list) ? this.list : this.value;
        }
      },

      watch: {
        options (newOptionValue){
          for(var property in newOptionValue) {
            if (readonlyProperties.indexOf(property)==-1) {
              this._sortable.option(property, newOptionValue[property] );
            }
          }
        },

        realList() {
          this.computeIndexes()
        }
      },

      methods: {
        getChildrenNodes () {
          const rawNodes = this.$slots.default
          return this.transitionMode? rawNodes[0].child.$slots.default : rawNodes
        },

        computeIndexes () {
          this.$nextTick( () => {
             this.visibleIndexes = computeIndexes(this.getChildrenNodes(), this.rootContainer.children)
          })
        },

        getUnderlyingVm (htmlElt) {
          const index = computeVmIndex(this.getChildrenNodes(), htmlElt)
          const element = this.realList[index]
          return {index, element}
        },

        getUnderlyingPotencialDraggableComponent ({__vue__}) {
          if (!__vue__ || !__vue__.$options || __vue__.$options._componentTag!=="transition-group"){
            return __vue__
          }
          return __vue__.$parent
        },

        emitChanges (evt) {
          this.$nextTick( ()=>{
            this.$emit('change', evt)
          });
        },

        alterList (onList) {
           if (!!this.list) {
            onList(this.list)
          }
          else {
            const newList = [...this.value]
            onList(newList)
            this.$emit('input', newList)
          }
        },

        spliceList () {
          const spliceList = list => list.splice(...arguments)
          this.alterList(spliceList)
        },

        updatePosition (oldIndex, newIndex) {
          const updatePosition = list => list.splice(newIndex, 0, list.splice(oldIndex, 1)[0])
          this.alterList(updatePosition)
        },

        getRelatedContextFromMoveEvent({to, related}) {
          const component = this.getUnderlyingPotencialDraggableComponent(to)
          if (!component) {
            return {component}
          }
          const list = component.realList
          const context = {list, component}
          if (to !== related && list && component.getUnderlyingVm) {
            const destination = component.getUnderlyingVm(related)
            return Object.assign(destination, context)
          }

          return context
        },

        getVmIndex (domIndex) {
          const indexes = this.visibleIndexes
          const numberIndexes = indexes.length
          return (domIndex > numberIndexes - 1) ? numberIndexes : indexes[domIndex]
        },

        onDragStart (evt) {
          this.context = this.getUnderlyingVm(evt.item)
          evt.item._underlying_vm_ = this.clone(this.context.element)
          draggingElement = evt.item
        },

        onDragAdd (evt) {
          const element = evt.item._underlying_vm_
          if (element === undefined) {
            return
          }
          removeNode(evt.item)
          const newIndex = this.getVmIndex(evt.newIndex)
          this.spliceList(newIndex, 0, element)
          this.computeIndexes()
          const added = {element, newIndex}
          this.emitChanges({added})
        },

        onDragRemove (evt) {
          insertNodeAt(this.rootContainer, evt.item, evt.oldIndex)
          if (this.isCloning) {
            removeNode(evt.clone)
            return
          }
          const oldIndex = this.context.index
          this.spliceList(oldIndex, 1)
          const removed = {element: this.context.element, oldIndex}
          this.emitChanges({removed})
        },

        onDragUpdate (evt) {
          removeNode(evt.item)
          insertNodeAt(evt.from, evt.item, evt.oldIndex)
          const oldIndex = this.context.index
          const newIndex = this.getVmIndex(evt.newIndex)
          this.updatePosition(oldIndex, newIndex)
          const moved = {element: this.context.element, oldIndex, newIndex}
          this.emitChanges({moved})
        },

        computeFutureIndex (relatedContext, evt) {
          if (!relatedContext.element){
            return 0
          }
          const domChildren = [...evt.to.children]
          const currentDOMIndex = domChildren.indexOf(evt.related)
          const currentIndex = relatedContext.component.getVmIndex(currentDOMIndex)
          const draggedInList = domChildren.indexOf(draggingElement) != -1
          return draggedInList? currentIndex : currentIndex+1
        },

        onDragMove (evt) {
          const onMove = this.move
          if (!onMove || !this.realList) {
            return true
          }

          const relatedContext = this.getRelatedContextFromMoveEvent(evt)
          const draggedContext = this.context
          const futureIndex = this.computeFutureIndex(relatedContext, evt)
          Object.assign(draggedContext, { futureIndex })
          Object.assign(evt, {relatedContext, draggedContext})
          return onMove(evt)
        },

        onDragEnd (evt) {
          this.computeIndexes()
          draggingElement = null
        }
      }
    }
    return draggableComponent
  }

  if (typeof exports == "object") {
    var Sortable =  require("sortablejs")
    module.exports = buildDraggable(Sortable)
  } else if (typeof define == "function" && define.amd) {
    define(['sortablejs'], function(Sortable) { return buildDraggable(Sortable);});
  } else if ( window && (window.Vue) && (window.Sortable)) {
    var draggable = buildDraggable(window.Sortable)
    Vue.component('draggable', draggable)
  }
})();
<commit_msg>pull of undefined fix<commit_after>(function() {
  "use strict";

  function buildDraggable(Sortable) {
    function removeNode (node) {
      node.parentElement.removeChild(node)
    }

    function insertNodeAt (fatherNode, node, position) {
      if (position < fatherNode.children.length) {
        fatherNode.insertBefore(node, fatherNode.children[position])
      } else {
        fatherNode.appendChild(node)
      }
    }

    function computeVmIndex (vnodes, element) {
      return vnodes.map(elt => elt.elm).indexOf(element)
    }

    function computeIndexes (slots, children) {
      return (!slots)? [] : Array.prototype.map.call(children, elt => computeVmIndex(slots, elt))
    }

    function emit (evtName, evtData) {
      this.$emit( evtName.toLowerCase(), evtData)
    }

    function delegateAndEmit (evtName) {
      return (evtData) => {
        if (this.realList!==null) {
          this['onDrag' + evtName](evtData)
        }
        emit.call(this, evtName, evtData)
      }
    }

    const eventsListened = ['Start', 'Add', 'Remove', 'Update', 'End']
    const eventsToEmit = ['Choose', 'Sort', 'Filter', 'Clone']
    const readonlyProperties = ['Move', ...eventsListened, ...eventsToEmit].map(evt => 'on'+evt)
    var draggingElement = null

    const props = {
      options: Object,
      list: {
        type: Array,
        required: false,
        default: null
      },
      value: {
        type: Array,
        required: false,
        default: null
      },
      clone: {
        type: Function,
        default : (original) => { return original;}
      },
      element: {
        type: String,
        default: 'div'
      },
      move: {
        type: Function,
        default: null
      }
    }

    const draggableComponent = {
      props,

      data() {
        return {
          transitionMode: false
        }
      },

      render (h) {
        if (this.$slots.default && this.$slots.default.length===1) {
          const child = this.$slots.default[0]
          if (child.componentOptions && child.componentOptions.tag==="transition-group") {
            this.transitionMode = true
          }
        }
        return h(this.element, null, this.$slots.default);
      },

      mounted () {
        var optionsAdded = {};
        eventsListened.forEach( elt => {
          optionsAdded['on' + elt] = delegateAndEmit.call(this, elt)
        });

        eventsToEmit.forEach( elt => {
          optionsAdded['on' + elt] = emit.bind(this, elt)
        });

        const options = Object.assign({}, this.options, optionsAdded, { onMove: evt => {return this.onDragMove(evt);} })
        this._sortable = new Sortable(this.rootContainer, options)
        this.computeIndexes()
      },

      beforeDestroy () {
        this._sortable.destroy()
      },

      computed : {
        rootContainer () {
          return this.transitionMode? this.$el.children[0] : this.$el;
        },

        isCloning () {
          return (!!this.options) && (this.options.group !== null) && (this.options.group !== undefined) && (this.options.group.pull === 'clone')
        },

        realList () {
          return (!!this.list) ? this.list : this.value;
        }
      },

      watch: {
        options (newOptionValue){
          for(var property in newOptionValue) {
            if (readonlyProperties.indexOf(property)==-1) {
              this._sortable.option(property, newOptionValue[property] );
            }
          }
        },

        realList() {
          this.computeIndexes()
        }
      },

      methods: {
        getChildrenNodes () {
          const rawNodes = this.$slots.default
          return this.transitionMode? rawNodes[0].child.$slots.default : rawNodes
        },

        computeIndexes () {
          this.$nextTick( () => {
             this.visibleIndexes = computeIndexes(this.getChildrenNodes(), this.rootContainer.children)
          })
        },

        getUnderlyingVm (htmlElt) {
          const index = computeVmIndex(this.getChildrenNodes(), htmlElt)
          const element = this.realList[index]
          return {index, element}
        },

        getUnderlyingPotencialDraggableComponent ({__vue__}) {
          if (!__vue__ || !__vue__.$options || __vue__.$options._componentTag!=="transition-group"){
            return __vue__
          }
          return __vue__.$parent
        },

        emitChanges (evt) {
          this.$nextTick( ()=>{
            this.$emit('change', evt)
          });
        },

        alterList (onList) {
           if (!!this.list) {
            onList(this.list)
          }
          else {
            const newList = [...this.value]
            onList(newList)
            this.$emit('input', newList)
          }
        },

        spliceList () {
          const spliceList = list => list.splice(...arguments)
          this.alterList(spliceList)
        },

        updatePosition (oldIndex, newIndex) {
          const updatePosition = list => list.splice(newIndex, 0, list.splice(oldIndex, 1)[0])
          this.alterList(updatePosition)
        },

        getRelatedContextFromMoveEvent({to, related}) {
          const component = this.getUnderlyingPotencialDraggableComponent(to)
          if (!component) {
            return {component}
          }
          const list = component.realList
          const context = {list, component}
          if (to !== related && list && component.getUnderlyingVm) {
            const destination = component.getUnderlyingVm(related)
            return Object.assign(destination, context)
          }

          return context
        },

        getVmIndex (domIndex) {
          const indexes = this.visibleIndexes
          const numberIndexes = indexes.length
          return (domIndex > numberIndexes - 1) ? numberIndexes : indexes[domIndex]
        },

        onDragStart (evt) {
          this.context = this.getUnderlyingVm(evt.item)
          evt.item._underlying_vm_ = this.clone(this.context.element)
          draggingElement = evt.item
        },

        onDragAdd (evt) {
          const element = evt.item._underlying_vm_
          if (element === undefined) {
            return
          }
          removeNode(evt.item)
          const newIndex = this.getVmIndex(evt.newIndex)
          this.spliceList(newIndex, 0, element)
          this.computeIndexes()
          const added = {element, newIndex}
          this.emitChanges({added})
        },

        onDragRemove (evt) {
          insertNodeAt(this.rootContainer, evt.item, evt.oldIndex)
          if (this.isCloning) {
            removeNode(evt.clone)
            return
          }
          const oldIndex = this.context.index
          this.spliceList(oldIndex, 1)
          const removed = {element: this.context.element, oldIndex}
          this.emitChanges({removed})
        },

        onDragUpdate (evt) {
          removeNode(evt.item)
          insertNodeAt(evt.from, evt.item, evt.oldIndex)
          const oldIndex = this.context.index
          const newIndex = this.getVmIndex(evt.newIndex)
          this.updatePosition(oldIndex, newIndex)
          const moved = {element: this.context.element, oldIndex, newIndex}
          this.emitChanges({moved})
        },

        computeFutureIndex (relatedContext, evt) {
          if (!relatedContext.element){
            return 0
          }
          const domChildren = [...evt.to.children]
          const currentDOMIndex = domChildren.indexOf(evt.related)
          const currentIndex = relatedContext.component.getVmIndex(currentDOMIndex)
          const draggedInList = domChildren.indexOf(draggingElement) != -1
          return draggedInList? currentIndex : currentIndex+1
        },

        onDragMove (evt) {
          const onMove = this.move
          if (!onMove || !this.realList) {
            return true
          }

          const relatedContext = this.getRelatedContextFromMoveEvent(evt)
          const draggedContext = this.context
          const futureIndex = this.computeFutureIndex(relatedContext, evt)
          Object.assign(draggedContext, { futureIndex })
          Object.assign(evt, {relatedContext, draggedContext})
          return onMove(evt)
        },

        onDragEnd (evt) {
          this.computeIndexes()
          draggingElement = null
        }
      }
    }
    return draggableComponent
  }

  if (typeof exports == "object") {
    var Sortable =  require("sortablejs")
    module.exports = buildDraggable(Sortable)
  } else if (typeof define == "function" && define.amd) {
    define(['sortablejs'], function(Sortable) { return buildDraggable(Sortable);});
  } else if ( window && (window.Vue) && (window.Sortable)) {
    var draggable = buildDraggable(window.Sortable)
    Vue.component('draggable', draggable)
  }
})();
<|endoftext|>


<commit_before>/**
 * dom storage adapter
 * ===
 * - originally authored by Joseph Pecoraro
 *
 */
//
// TODO does it make sense to be chainable all over the place?
// chainable: nuke, remove, all, get, save, all
// not chainable: valid, keys
//
Lawnchair.adapter('dom', (function() {
    var storage = window.localStorage
    // the indexer is an encapsulation of the helpers needed to keep an ordered index of the keys
    var indexer = function(name) {
        return {
            // the key
            key: name + '._index_',
            // returns the index
            all: function() {
				var a  = storage.getItem(this.key)
				if (a) {
					a = JSON.parse(a)
				}
                if (a === null) storage.setItem(this.key, JSON.stringify([])) // lazy init
                return JSON.parse(storage.getItem(this.key))
            },
            // adds a key to the index
            add: function (key) {
                var a = this.all()
                a.push(key)
                storage.setItem(this.key, JSON.stringify(a))
            },
            // deletes a key from the index
            del: function (key) {
                var a = this.all(), r = []
                // FIXME this is crazy inefficient but I'm in a strata meeting and half concentrating
                for (var i = 0, l = a.length; i < l; i++) {
                    if (a[i] != key) r.push(a[i])
                }
                storage.setItem(this.key, JSON.stringify(r))
            },
            // returns index for a key
            find: function (key) {
                var a = this.all()
                for (var i = 0, l = a.length; i < l; i++) {
                    if (key === a[i]) return i
                }
                return false
            }
        }
    }

    // adapter api
    return {

        // ensure we are in an env with localStorage
        valid: function () {
            return !!storage && function() {
              // in mobile safari if safe browsing is enabled, window.storage
              // is defined but setItem calls throw exceptions.
              var success = true
              var value = Math.random()
              try {
                storage.setItem(value, value)
              } catch (e) {
                success = false
              }
              storage.removeItem(value)
              return success
            }()
        },

        init: function (options, callback) {
            this.indexer = indexer(this.name)
            if (callback) this.fn(this.name, callback).call(this, this)
        },

        save: function (obj, callback) {
            var key = obj.key ? this.name + '.' + obj.key : this.name + '.' + this.uuid()
            // if the key is not in the index push it on
            if (this.indexer.find(key) === false) this.indexer.add(key)
            // now we kil the key and use it in the store colleciton
            delete obj.key;
            storage.setItem(key, JSON.stringify(obj))
            obj.key = key.slice(this.name.length + 1)
            if (callback) {
                this.lambda(callback).call(this, obj)
            }
            return this
        },

        batch: function (ary, callback) {
            var saved = []
            // not particularily efficient but this is more for sqlite situations
            for (var i = 0, l = ary.length; i < l; i++) {
                this.save(ary[i], function(r){
                    saved.push(r)
                })
            }
            if (callback) this.lambda(callback).call(this, saved)
            return this
        },

        // accepts [options], callback
        keys: function(callback) {
            if (callback) {
                var name = this.name
                ,   keys = this.indexer.all().map(function(r){ return r.replace(name + '.', '') })
                this.fn('keys', callback).call(this, keys)
            }
            return this // TODO options for limit/offset, return promise
        },

        get: function (key, callback) {
            if (this.isArray(key)) {
                var r = []
                for (var i = 0, l = key.length; i < l; i++) {
                    var k = this.name + '.' + key[i]
                    var obj = storage.getItem(k)
                    if (obj) {
						obj = JSON.parse(obj)
                        obj.key = key[i]
                        r.push(obj)
                    }
                }
                if (callback) this.lambda(callback).call(this, r)
            } else {
                var k = this.name + '.' + key
                var  obj = storage.getItem(k)
                if (obj) {
					obj = JSON.parse(obj)
					obj.key = key
				}
                if (callback) this.lambda(callback).call(this, obj)
            }
            return this
        },

        exists: function (key, cb) {
            var exists = this.indexer.find(this.name+'.'+key) === false ? false : true ;
            this.lambda(cb).call(this, exists);
            return this;
        },
        // NOTE adapters cannot set this.__results but plugins do
        // this probably should be reviewed
        all: function (callback) {
            var idx = this.indexer.all()
            ,   r   = []
            ,   o
            ,   k
            for (var i = 0, l = idx.length; i < l; i++) {
                k     = idx[i] //v
                o     = JSON.parse(storage.getItem(k))
                o.key = k.replace(this.name + '.', '')
                r.push(o)
            }
            if (callback) this.fn(this.name, callback).call(this, r)
            return this
        },

        remove: function (keyOrObj, callback) {
            var key = this.name + '.' + ((keyOrObj.key) ? keyOrObj.key : keyOrObj)
            this.indexer.del(key)
            storage.removeItem(key)
            if (callback) this.lambda(callback).call(this)
            return this
        },

        nuke: function (callback) {
            this.all(function(r) {
                for (var i = 0, l = r.length; i < l; i++) {
                    this.remove(r[i]);
                }
                if (callback) this.lambda(callback).call(this)
            })
            return this
        }
}})());
<commit_msg>fallback for browsers(<ie9) which does not support Array.map.<commit_after>/**
 * dom storage adapter
 * ===
 * - originally authored by Joseph Pecoraro
 *
 */
//
// TODO does it make sense to be chainable all over the place?
// chainable: nuke, remove, all, get, save, all
// not chainable: valid, keys
//
Lawnchair.adapter('dom', (function() {
    var storage = window.localStorage
    // the indexer is an encapsulation of the helpers needed to keep an ordered index of the keys
    var indexer = function(name) {
        return {
            // the key
            key: name + '._index_',
            // returns the index
            all: function() {
				var a  = storage.getItem(this.key)
				if (a) {
					a = JSON.parse(a)
				}
                if (a === null) storage.setItem(this.key, JSON.stringify([])) // lazy init
                return JSON.parse(storage.getItem(this.key))
            },
            // adds a key to the index
            add: function (key) {
                var a = this.all()
                a.push(key)
                storage.setItem(this.key, JSON.stringify(a))
            },
            // deletes a key from the index
            del: function (key) {
                var a = this.all(), r = []
                // FIXME this is crazy inefficient but I'm in a strata meeting and half concentrating
                for (var i = 0, l = a.length; i < l; i++) {
                    if (a[i] != key) r.push(a[i])
                }
                storage.setItem(this.key, JSON.stringify(r))
            },
            // returns index for a key
            find: function (key) {
                var a = this.all()
                for (var i = 0, l = a.length; i < l; i++) {
                    if (key === a[i]) return i
                }
                return false
            }
        }
    }

    // adapter api
    return {

        // ensure we are in an env with localStorage
        valid: function () {
            return !!storage && function() {
              // in mobile safari if safe browsing is enabled, window.storage
              // is defined but setItem calls throw exceptions.
              var success = true
              var value = Math.random()
              try {
                storage.setItem(value, value)
              } catch (e) {
                success = false
              }
              storage.removeItem(value)
              return success
            }()
        },

        init: function (options, callback) {
            this.indexer = indexer(this.name)
            if (callback) this.fn(this.name, callback).call(this, this)
        },

        save: function (obj, callback) {
            var key = obj.key ? this.name + '.' + obj.key : this.name + '.' + this.uuid()
            // if the key is not in the index push it on
            if (this.indexer.find(key) === false) this.indexer.add(key)
            // now we kil the key and use it in the store colleciton
            delete obj.key;
            storage.setItem(key, JSON.stringify(obj))
            obj.key = key.slice(this.name.length + 1)
            if (callback) {
                this.lambda(callback).call(this, obj)
            }
            return this
        },

        batch: function (ary, callback) {
            var saved = []
            // not particularily efficient but this is more for sqlite situations
            for (var i = 0, l = ary.length; i < l; i++) {
                this.save(ary[i], function(r){
                    saved.push(r)
                })
            }
            if (callback) this.lambda(callback).call(this, saved)
            return this
        },

        // accepts [options], callback
        keys: function(callback) {
            if (callback) {
                var name = this.name
                var indices = this.indexer.all();
                var keys = [];
                //Checking for the support of map.
                if(Array.prototype.map) {
                    keys = indices.map(function(r){ return r.replace(name + '.', '') })
                } else {
                    for (var key in indices) {
                        keys.push(key.replace(name + '.', ''));
                    }
                }
                this.fn('keys', callback).call(this, keys)
            }
            return this // TODO options for limit/offset, return promise
        },

        get: function (key, callback) {
            if (this.isArray(key)) {
                var r = []
                for (var i = 0, l = key.length; i < l; i++) {
                    var k = this.name + '.' + key[i]
                    var obj = storage.getItem(k)
                    if (obj) {
						obj = JSON.parse(obj)
                        obj.key = key[i]
                        r.push(obj)
                    }
                }
                if (callback) this.lambda(callback).call(this, r)
            } else {
                var k = this.name + '.' + key
                var  obj = storage.getItem(k)
                if (obj) {
					obj = JSON.parse(obj)
					obj.key = key
				}
                if (callback) this.lambda(callback).call(this, obj)
            }
            return this
        },

        exists: function (key, cb) {
            var exists = this.indexer.find(this.name+'.'+key) === false ? false : true ;
            this.lambda(cb).call(this, exists);
            return this;
        },
        // NOTE adapters cannot set this.__results but plugins do
        // this probably should be reviewed
        all: function (callback) {
            var idx = this.indexer.all()
            ,   r   = []
            ,   o
            ,   k
            for (var i = 0, l = idx.length; i < l; i++) {
                k     = idx[i] //v
                o     = JSON.parse(storage.getItem(k))
                o.key = k.replace(this.name + '.', '')
                r.push(o)
            }
            if (callback) this.fn(this.name, callback).call(this, r)
            return this
        },

        remove: function (keyOrObj, callback) {
            var key = this.name + '.' + ((keyOrObj.key) ? keyOrObj.key : keyOrObj)
            this.indexer.del(key)
            storage.removeItem(key)
            if (callback) this.lambda(callback).call(this)
            return this
        },

        nuke: function (callback) {
            this.all(function(r) {
                for (var i = 0, l = r.length; i < l; i++) {
                    this.remove(r[i]);
                }
                if (callback) this.lambda(callback).call(this)
            })
            return this
        }
}})());
<|endoftext|>


<commit_before>var fs = require('fs'),
    http = require('http'),
    log4js = require('log4js'),
    mime = require('mime'),
    nowjs = require('now'),
    path = require('path'),
    util = require('util');

// Put get logger to console
console.logger = log4js.getLogger

// Parse configuration
var config_file = '';
var config = null;
var reload = function (current, previous) {
  var _current = current.mtime.getTime();
  var _previous = previous.mtime.getTime();

  if (_current != _previous) {
    var config_path = path.join(__dirname, config_file);
    delete require.cache[config_path];

    config = require(config_file).config;
    log4js.configure(config.log);

    console.info ('Reload log configuration success');
  }
}

if (path.existsSync('config.js')) {
  config = require('./config.js').config;
  config_file = './config.js';
  fs.watchFile('config.js', reload);

  log4js.configure(config.log);
} else {
  config = require('./config-default.js').config;
  config_file = './config-default.js';
  fs.watchFile('config-default.js', reload);

  log4js.configure(config.log);
}

// Initial store
var Store = require('./model/store.js').store;
var store = new Store(config.mongo);

// Initial server and now.js
var httpServer = http.createServer(
  function(request, response) {

    var incoming = request.headers['x-forwarded-for'] ||
                   request.connection.remoteAddress;
    console.log ('(' + incoming + ') request: ' + request.url);

    var url = request.url == '/' ? '/index.html' : request.url;
    var filePath = path.join(__dirname, config.base, url);

    var _notfound = function(request, response) {
      // Change to serve file instead
      response.writeHead(404, {});
      response.end('notfound');
    }

    if (path.existsSync(filePath)) {
      // Serve static file
      var stat = fs.statSync(filePath);
      if (stat.isDirectory()) {
        _notfound(request, response);
      } else {
        response.writeHead(200, {
          'Content-Type': mime.lookup(filePath),
          'Content-Length': stat.size
        });

        var stream = fs.createReadStream(filePath);
        stream.on('data', function (data) {
          response.write(data);
        });

        stream.on('end', function() {
          response.end();
        });
      }

    } else {
      _notfound(request, response);
    }

  });
httpServer.listen(config.port);
console.info ('Listen to ' + config.port);

var everyoneLogger = log4js.getLogger('socketio');
var everyone = nowjs.initialize(httpServer,
  { socketio: {
      logger: { log: everyoneLogger.log,
                info: everyoneLogger.info,
                debug: everyoneLogger.debug,
                warn: everyoneLogger.warn,
                error: everyoneLogger.error,
                isLevelEnabled: function(otherLevel) {
                  return everyoneLogger.level.isLessThanOrEqualTo(otherLevel);
                },
                emit: function(level, event) {
                  event.categoryName = everyoneLogger.category;
                  everyoneLogger.emit(level, event);
                } } } });

// Initial everyone
for (var index = 0; index < config.everyone.length; index++) {
  config.everyone[index] (everyone.now, store);
}
console.info ('Everyone is listen to everybody, ready!');
<commit_msg>Disable polling.<commit_after>var fs = require('fs'),
    http = require('http'),
    log4js = require('log4js'),
    mime = require('mime'),
    nowjs = require('now'),
    path = require('path'),
    util = require('util');

// Put get logger to console
console.logger = log4js.getLogger

// Parse configuration
var config_file = '';
var config = null;
var reload = function (current, previous) {
  var _current = current.mtime.getTime();
  var _previous = previous.mtime.getTime();

  if (_current != _previous) {
    var config_path = path.join(__dirname, config_file);
    delete require.cache[config_path];

    config = require(config_file).config;
    log4js.configure(config.log);

    console.info ('Reload log configuration success');
  }
}

if (path.existsSync('config.js')) {
  config = require('./config.js').config;
  config_file = './config.js';
  fs.watchFile('config.js', reload);

  log4js.configure(config.log);
} else {
  config = require('./config-default.js').config;
  config_file = './config-default.js';
  fs.watchFile('config-default.js', reload);

  log4js.configure(config.log);
}

// Initial store
var Store = require('./model/store.js').store;
var store = new Store(config.mongo);

// Initial server and now.js
var httpServer = http.createServer(
  function(request, response) {

    var incoming = request.headers['x-forwarded-for'] ||
                   request.connection.remoteAddress;
    console.log ('(' + incoming + ') request: ' + request.url);

    var url = request.url == '/' ? '/index.html' : request.url;
    var filePath = path.join(__dirname, config.base, url);

    var _notfound = function(request, response) {
      // Change to serve file instead
      response.writeHead(404, {});
      response.end('notfound');
    }

    if (path.existsSync(filePath)) {
      // Serve static file
      var stat = fs.statSync(filePath);
      if (stat.isDirectory()) {
        _notfound(request, response);
      } else {
        response.writeHead(200, {
          'Content-Type': mime.lookup(filePath),
          'Content-Length': stat.size
        });

        var stream = fs.createReadStream(filePath);
        stream.on('data', function (data) {
          response.write(data);
        });

        stream.on('end', function() {
          response.end();
        });
      }

    } else {
      _notfound(request, response);
    }

  });
httpServer.listen(config.port);
console.info ('Listen to ' + config.port);

var everyoneLogger = log4js.getLogger('socketio');
var everyone = nowjs.initialize(httpServer,
  { socketio: {
      transports: ['websocket', 'flashsocket', 'htmlfile'],
      logger: { log: everyoneLogger.log,
                info: everyoneLogger.info,
                debug: everyoneLogger.debug,
                warn: everyoneLogger.warn,
                error: everyoneLogger.error,
                isLevelEnabled: function(otherLevel) {
                  return everyoneLogger.level.isLessThanOrEqualTo(otherLevel);
                },
                emit: function(level, event) {
                  event.categoryName = everyoneLogger.category;
                  everyoneLogger.emit(level, event);
                } } } });

// Initial everyone
for (var index = 0; index < config.everyone.length; index++) {
  config.everyone[index] (everyone.now, store);
}
console.info ('Everyone is listen to everybody, ready!');
<|endoftext|>


<commit_before>var NoMatch = function(path) {
    this.path = path;
};
NoMatch.prototype = new Error();

var NoReverseMatch = function(path) {
    this.path = path;
};
NoReverseMatch.prototype = new Error();

var ReverseRegex = function(original, tokens, minArgs) {
    this.original = original;
    this.tokens = tokens;
    this.minArgs = minArgs;
};

ReverseRegex.prototype.apply = function(args) {
    if(args.length !== this.minArgs) {
        throw new NoReverseMatch();
    }

    var currentArg = 0,
        token,
        output = [];

    for(var i = 0, len = this.tokens.length; i < len; ++i) {
        token = this.tokens[i];
        if(token.length > 0) {
            output.push(token(args[currentArg++]));
        } else {
            output.push(token());
        }
    }
    return output.join('');
};

ReverseRegex.prototype.test = function(args) {
    try {
        this.apply(args);
    } catch(err) {
        if(err instanceof NoReverseMatch) {
            return false;
        } else throw err;
    }
    return true;
};

var createReverseRegex = function(fromSource) {
    fromSource = fromSource.replace(/[\^\$]/g, '');     // get rid of "line begin / line end" indicators
    var tokens = [],
        src = fromSource,
        re = /\((.*?)\)/,
        match,
        groupsToMatch = 0,
        text = function(str) {
            return function () { return str.replace(/(\.|\*|\+|\\\w)/g, ''); };// remove ungrouped regex-ery
        },
        regex = function(exp) {
            ++groupsToMatch;
            return function(value) {
                if((new RegExp('^'+exp+'$')).test(value)) {
                    return value;
                } else throw new NoReverseMatch();
            };
        };
    while(src.length) {
        match = re.exec(src);
        if(match) {
            tokens.push(text(src.slice(0, match.index)));
            tokens.push(regex(src.slice(match.index, match.index + match[0].length)));
            src = src.slice(match.index + match[0].length);
        } else {
            tokens.push(text(src));
            src = '';
        }
    }
    return new ReverseRegex(fromSource, tokens, groupsToMatch);
};

var Route = function(re, target, name) {
    this.re = new RegExp(re);
    this.target = target;
    this.name = name;
};

Route.prototype.getTarget = function(base_path) {
    if(typeof(this.target) === 'string') {
        var full_path = base_path.length > 0 ? base_path.split('.').concat(this.target.split('.')) : this.target.split('.'),
            app_name = full_path[0],
            rest = full_path.slice(1),
            retval;
        try {
            retval = require(app_name);
        } catch(err) {
            var path = require.paths.join(",\n\t");
            throw new Error('Could not find module "'+app_name+'"!\n\tIs it on path?\n\t'+path);
        }

        try {
            for(var i = 0, len = rest.length; i < len; ++i) {
                retval = retval[rest[i]];
            }
        } catch(err) {
            throw new Error('Found app named "'+app_name+'", but could not load "'+rest.join('.')+'"!');
        }

        this.target = retval;
    }
    return this.target;
};

var Router = function(base_path, routes, optional_target_object) {
    this.base_path = base_path;
    this.routes = routes;
    this.length = this.routes.length;
    this.reverseMatch = {};
    this.optional_target_object = optional_target_object || {};
};

Router.prototype.reverseMatch = {};

Router.prototype.reverse = function(name, args, source) {
    var target,
        reverseRegex;
    source = source === undefined ? '' : source;

    for(var i = 0; i < this.length; ++i) {
        target = this.routes[i].getTarget(this.base_path);
        if(this.routes[i].name === name) {
            reverseRegex = createReverseRegex(source+this.routes[i].re.source);
            if(reverseRegex.test(args)) {
                Router.prototype.reverseMatch[name] = reverseRegex;
                return reverseRegex.apply(args);
            } else {
                throw NoReverseMatch();
            }
        } else if(target instanceof Router) {
            try {
                return target.reverse(name, args, source+this.routes[i].re.source);
            } catch(err) {
                if(!(err instanceof NoReverseMatch)) {
                    throw err;
                }
            }
        }
    }
    throw new NoReverseMatch();
};

Router.prototype.match = function(path) {
    var match, target;

    for(var i = 0; i < this.length; ++i) {
        match = this.routes[i].re.exec(path);
        if(match) {
            target = this.routes[i].getTarget(this.base_path);
            if(target instanceof Router) {
                try {
                    return target.match(path.slice(match[0].length));
                } catch(err) {
                    if(!err instanceof NoMatch) {
                        throw err;
                    }
                }
            } else {
                var target_obj = this.optional_target_object;
                return function() {
                    return target.apply(target_obj, Array.prototype.slice.apply(arguments).concat(match.slice(1)));
                };
            }
        }
    }
    throw new NoMatch(path);
};

exports.url = function (re, target, name) {
    return new Route(re, target, name);
};
exports.surl = function(re, target, name) {
    return new Route(re.replace(/:/g, '\\'), target, name);
};
exports.routes = function(base_path) {
    var routes = Array.prototype.slice.call(arguments, 1);
    return new Router(base_path, routes);
};

exports.Route = Route;
exports.Router = Router;
exports.NoMatch = NoMatch;
exports.NoReverseMatch = NoReverseMatch;
exports.createReverseRegex = createReverseRegex;
<commit_msg>keep a list of routes attempted<commit_after>var NoMatch = function(path, tried) {
    this.path = path;
    this.urlpatterns = tried;
};
NoMatch.prototype = new Error();

var NoReverseMatch = function(path) {
    this.path = path;
};
NoReverseMatch.prototype = new Error();

var ReverseRegex = function(original, tokens, minArgs) {
    this.original = original;
    this.tokens = tokens;
    this.minArgs = minArgs;
};

ReverseRegex.prototype.apply = function(args) {
    if(args.length !== this.minArgs) {
        throw new NoReverseMatch();
    }

    var currentArg = 0,
        token,
        output = [];

    for(var i = 0, len = this.tokens.length; i < len; ++i) {
        token = this.tokens[i];
        if(token.length > 0) {
            output.push(token(args[currentArg++]));
        } else {
            output.push(token());
        }
    }
    return output.join('');
};

ReverseRegex.prototype.test = function(args) {
    try {
        this.apply(args);
    } catch(err) {
        if(err instanceof NoReverseMatch) {
            return false;
        } else throw err;
    }
    return true;
};

var createReverseRegex = function(fromSource) {
    fromSource = fromSource.replace(/[\^\$]/g, '');     // get rid of "line begin / line end" indicators
    var tokens = [],
        src = fromSource,
        re = /\((.*?)\)/,
        match,
        groupsToMatch = 0,
        text = function(str) {
            return function () { return str.replace(/(\.|\*|\+|\\\w)/g, ''); };// remove ungrouped regex-ery
        },
        regex = function(exp) {
            ++groupsToMatch;
            return function(value) {
                if((new RegExp('^'+exp+'$')).test(value)) {
                    return value;
                } else throw new NoReverseMatch();
            };
        };
    while(src.length) {
        match = re.exec(src);
        if(match) {
            tokens.push(text(src.slice(0, match.index)));
            tokens.push(regex(src.slice(match.index, match.index + match[0].length)));
            src = src.slice(match.index + match[0].length);
        } else {
            tokens.push(text(src));
            src = '';
        }
    }
    return new ReverseRegex(fromSource, tokens, groupsToMatch);
};

var Route = function(re, target, name) {
    this.re = new RegExp(re);
    this.target = target;
    this.name = name;
};

Route.prototype.getTarget = function(base_path) {
    if(typeof(this.target) === 'string') {
        var full_path = base_path.length > 0 ? base_path.split('.').concat(this.target.split('.')) : this.target.split('.'),
            app_name = full_path[0],
            rest = full_path.slice(1),
            retval;
        try {
            retval = require(app_name);
        } catch(err) {
            var path = require.paths.join(",\n\t");
            throw new Error('Could not find module "'+app_name+'"!\n\tIs it on path?\n\t'+path);
        }

        try {
            for(var i = 0, len = rest.length; i < len; ++i) {
                retval = retval[rest[i]];
            }
        } catch(err) {
            throw new Error('Found app named "'+app_name+'", but could not load "'+rest.join('.')+'"!');
        }

        this.target = retval;
    }
    return this.target;
};

var Router = function(base_path, routes, optional_target_object) {
    this.base_path = base_path;
    this.routes = routes;
    this.length = this.routes.length;
    this.reverseMatch = {};
    this.optional_target_object = optional_target_object || {};
};

Router.prototype.reverseMatch = {};

Router.prototype.reverse = function(name, args, source) {
    var target,
        reverseRegex;
    source = source === undefined ? '' : source;

    for(var i = 0; i < this.length; ++i) {
        target = this.routes[i].getTarget(this.base_path);
        if(this.routes[i].name === name) {
            reverseRegex = createReverseRegex(source+this.routes[i].re.source);
            if(reverseRegex.test(args)) {
                Router.prototype.reverseMatch[name] = reverseRegex;
                return reverseRegex.apply(args);
            } else {
                throw NoReverseMatch();
            }
        } else if(target instanceof Router) {
            try {
                return target.reverse(name, args, source+this.routes[i].re.source);
            } catch(err) {
                if(!(err instanceof NoReverseMatch)) {
                    throw err;
                }
            }
        }
    }
    throw new NoReverseMatch();
};

Router.prototype.match = function(path) {
    var match, target, tried = [];

    for(var i = 0; i < this.length; ++i) {
        var src = this.routes[i].re.source;
        match = this.routes[i].re.exec(path);
        tried.push(src);
        if(match) {
            target = this.routes[i].getTarget(this.base_path);
            if(target instanceof Router) {
                try {
                    return target.match(path.slice(match[0].length));
                } catch(err) {
                    if(!err instanceof NoMatch) {
                        throw err;
                    } else {
                        tried = tried.concat(err.urlpatterns.map(function(item) {
                            return [src, item].join('');
                        }));
                    }
                }
            } else {
                var target_obj = this.optional_target_object;
                return function() {
                    return target.apply(target_obj, Array.prototype.slice.apply(arguments).concat(match.slice(1)));
                };
            }
        }
    }
    throw new NoMatch(path, tried);
};

exports.url = function (re, target, name) {
    return new Route(re, target, name);
};
exports.surl = function(re, target, name) {
    return new Route(re.replace(/:/g, '\\'), target, name);
};
exports.routes = function(base_path) {
    var routes = Array.prototype.slice.call(arguments, 1);
    return new Router(base_path, routes);
};

exports.Route = Route;
exports.Router = Router;
exports.NoMatch = NoMatch;
exports.NoReverseMatch = NoReverseMatch;
exports.createReverseRegex = createReverseRegex;
<|endoftext|>


<commit_before>var mag = mag || {}
var noop = function() {}

var type = {}.toString
var ARRAY = '[object Array]'
var OBJECT = '[object Object]'
var STRING = "[object String]"

var $location = location,
  $document = document

var modes = {
  pathname: "",
  hash: "#",
  search: "?"
};
var redirect = noop,
  routeParams, currentRoute, isDefaultRoute = false;
mag.route = function() {
  //m.route()
  if (arguments.length === 0) return currentRoute;
  //m.route(el, defaultRoute, routes)
  else if (arguments.length === 3 && type.call(arguments[1]) === STRING) {
    var root = arguments[0],
      defaultRoute = arguments[1],
      router = arguments[2];
    redirect = function(source) {
      var path = currentRoute = normalizeRoute(source);
      if (!routeByValue(root, router, path)) {
        if (isDefaultRoute) throw new Error("Ensure the default route matches one of the routes defined in mag.route")
        isDefaultRoute = true
        mag.route(defaultRoute, true)
        isDefaultRoute = false
      }
    };
    var listener = mag.route.mode === "hash" ? "onhashchange" : "onpopstate";
    window[listener] = function() {
      var path = $location[mag.route.mode]
      if (mag.route.mode === "pathname") path += $location.search
      if (currentRoute != normalizeRoute(path)) {
        redirect(path)
      }
    };
    computePreRedrawHook = setScroll;

    //addLinks(root.children)

    window[listener]()
  }
  //config: m.route
  else if (arguments[0].addEventListener || arguments[0].attachEvent) {
    var element = arguments[0];
    var isInitialized = arguments[1];
    var context = arguments[2];
    var index = arguments[3];
    //console.log('CONFIG', isInitialized)
    element.href = (mag.route.mode !== 'pathname' ? $location.pathname : '') + modes[mag.route.mode] + element.getAttribute('href');
    if (element.addEventListener) {
      element.removeEventListener("click", routeUnobtrusive.bind({}, element.href));
      element.addEventListener("click", routeUnobtrusive.bind({}, element.href))
    } else {
      element.detachEvent("onclick", routeUnobtrusive.bind({}, element.href));
      element.attachEvent("onclick", routeUnobtrusive.bind({}, element.href))
    }
  }
  //m.route(route, params, shouldReplaceHistoryEntry)
  else if (type.call(arguments[0]) === STRING) {
    var oldRoute = currentRoute;
    currentRoute = arguments[0];
    var args = arguments[1] || {}
    var queryIndex = currentRoute.indexOf("?")
    var params = queryIndex > -1 ? parseQueryString(currentRoute.slice(queryIndex + 1)) : {}
    for (var i in args) params[i] = args[i]
    var querystring = buildQueryString(params)
    var currentPath = queryIndex > -1 ? currentRoute.slice(0, queryIndex) : currentRoute
    if (querystring) currentRoute = currentPath + (currentPath.indexOf("?") === -1 ? "?" : "&") + querystring;

    var shouldReplaceHistoryEntry = (arguments.length === 3 ? arguments[2] : arguments[1]) === true || oldRoute === arguments[0];

    if (window.history.pushState) {
      //console.log('history', shouldReplaceHistoryEntry, arguments.length, oldRoute, arguments[0], modes[mag.route.mode] + currentRoute)

      computePreRedrawHook = setScroll
      computePostRedrawHook = function() {
        window.history[shouldReplaceHistoryEntry ? "replaceState" : "pushState"](null, $document.title, modes[mag.route.mode] + currentRoute);
      };
      redirect(modes[mag.route.mode] + currentRoute)

      // should be run in didupdate
      computePostRedrawHook()
    } else {
      $location[mag.route.mode] = currentRoute
      redirect(modes[mag.route.mode] + currentRoute)
    }
  }
};
mag.route.param = function(key) {
  if (!routeParams) throw new Error("You must call m.route(element, defaultRoute, routes) before calling m.route.param()")
  return routeParams[key]
};
mag.route.mode = "search";

function normalizeRoute(route) {
  return route.slice(modes[mag.route.mode].length)
}

// function addLinks(items) {
//   //var items = document.querySelectorAll('.link')
//   for (var i = items.length; i--;) {

//     var element = items[i]
//     if (element.children) addLinks(element.children)
//     if (!element.hasAttribute('href') || element.getAttribute('config') != 'route') continue

//     element.href = (mag.route.mode !== 'pathname' ? $location.pathname : '') + modes[mag.route.mode] + element.getAttribute('href');

//     //console.log('href', element.href)
//     element.addEventListener("click", routeUnobtrusive)
//   }
// }

function routeByValue(root, router, path) {
  routeParams = {};

  var queryStart = path.indexOf("?");
  if (queryStart !== -1) {
    routeParams = parseQueryString(path.substr(queryStart + 1, path.length));
    path = path.substr(0, queryStart)
  }

  // Get all routes and check if there's
  // an exact match for the current path
  var keys = Object.keys(router);
  var index = keys.indexOf(path);
  if (index !== -1) {
    router[keys[index]]()
    //console.log(path, root, routeParams)
    return true;
  }

  for (var route in router) {
    if (route === path) {
      router[route]()
      //console.log('MOUNT2!!!', root, routeParams)
      return true
    }

    var matcher = new RegExp("^" + route.replace(/:[^\/]+?\.{3}/g, "(.*?)").replace(/:[^\/]+/g, "([^\\/]+)") + "\/?$");

    if (matcher.test(path)) {
      path.replace(matcher, function() {
        var keys = route.match(/:[^\/]+/g) || [];
        var values = [].slice.call(arguments, 1, -2);
        for (var i = 0, len = keys.length; i < len; i++) routeParams[keys[i].replace(/:|\./g, "")] = decodeURIComponent(values[i])
        router[route]()
        //console.log(path, root, routeParams)
      });
      return true
    }
  }
}

function routeUnobtrusive(href, e) {

  e = e || event;
  if (e.ctrlKey || e.metaKey || e.which === 2) return;
  if (e.preventDefault) e.preventDefault();
  else e.returnValue = false;
  var currentTarget = e.currentTarget || e.srcElement;
  var args = mag.route.mode === "pathname" && currentTarget.search ? parseQueryString(currentTarget.search.slice(1)) : {};
  while (currentTarget && currentTarget.nodeName.toUpperCase() != "A") {
    currentTarget = currentTarget.parentNode
  }

  mag.route(currentTarget[mag.route.mode].slice(modes[mag.route.mode].length), args)
  if (currentTarget.href != href) {
    //WHAT?
    //currentTarget.href = href
  }

}

function setScroll() {
  if (mag.route.mode != "hash" && $location.hash) $location.hash = $location.hash;
  else window.scrollTo(0, 0)
}

function buildQueryString(object, prefix) {
  var duplicates = {}
  var str = []
  for (var prop in object) {
    var key = prefix ? prefix + "[" + prop + "]" : prop
    var value = object[prop]
    var valueType = type.call(value)
    var pair = (value === null) ? encodeURIComponent(key) :
      valueType === OBJECT ? buildQueryString(value, key) :
      valueType === ARRAY ? value.reduce(function(memo, item) {
        if (!duplicates[key]) duplicates[key] = {}
        if (!duplicates[key][item]) {
          duplicates[key][item] = true
          return memo.concat(encodeURIComponent(key) + "=" + encodeURIComponent(item))
        }
        return memo
      }, []).join("&") :
      encodeURIComponent(key) + "=" + encodeURIComponent(value)
    if (value !== undefined) str.push(pair)
  }
  return str.join("&")
}

function parseQueryString(str) {
  if (str.charAt(0) === "?") str = str.substring(1);

  var pairs = str.split("&"),
    params = {};
  for (var i = 0, len = pairs.length; i < len; i++) {
    var pair = pairs[i].split("=");
    var key = decodeURIComponent(pair[0])
    var value = pair.length == 2 ? decodeURIComponent(pair[1]) : null
    if (params[key] != null) {
      if (type.call(params[key]) !== ARRAY) params[key] = [params[key]]
      params[key].push(value)
    } else params[key] = value
  }
  return params
}
mag.route.buildQueryString = buildQueryString
mag.route.parseQueryString = parseQueryString

/*
//http://jsbin.com/magimoyodi/edit

        {{country.name}}

 */

/*
var links = [{
  text: 'Home',
  link: '/'
}, {
  text: 'Login',
  link: '/userid1232'
}]

mag.module('app', {
  controller: function(props) {},
  view: function(state, props) {
    route(state, props.links)

  }
}, {
  links: links
})

function route(data, links) {
  mag.route(data.app, "/", {
    "/": function() {
      console.log('test')
      data.countries = links.map(function(item) {
        return {
          a: {
            _config: mag.route,
            _href: item.link,
            _text: item.text
          }
        }
      })

      data.content = 'HOME'
    },
    "/:login": function() {
      data.content = 'LOGIN - ' + mag.route.param('login')
    }
  });

}
*/
// mag.route.call({},document.querySelector('a:first-child'),true,{},0)
// mag.route.call({},document.querySelector('a:nth-child(2)'),true,{},0)
<commit_msg>Update route.js<commit_after>var mag = mag || {}
var noop = function() {}

var type = {}.toString
var ARRAY = '[object Array]'
var OBJECT = '[object Object]'
var STRING = "[object String]"

var $location = location,
  $document = document

var modes = {
  pathname: "",
  hash: "#",
  search: "?"
};
var redirect = noop,
  routeParams, currentRoute, isDefaultRoute = false;
mag.route = function() {
  //m.route()
  if (arguments.length === 0) return currentRoute;
  //m.route(el, defaultRoute, routes)
  else if (arguments.length === 3 && type.call(arguments[1]) === STRING) {
    var root = arguments[0],
      defaultRoute = arguments[1],
      router = arguments[2];
    redirect = function(source) {
      var path = currentRoute = normalizeRoute(source);
      if (!routeByValue(root, router, path)) {
        if (isDefaultRoute) throw new Error("Ensure the default route matches one of the routes defined in mag.route")
        isDefaultRoute = true
        mag.route(defaultRoute, true)
        isDefaultRoute = false
      }
    };
    var listener = mag.route.mode === "hash" ? "onhashchange" : "onpopstate";
    window[listener] = function() {
      var path = $location[mag.route.mode]
      if (mag.route.mode === "pathname") path += $location.search
      if (currentRoute != normalizeRoute(path)) {
        redirect(path)
      }
    };
    computePreRedrawHook = setScroll;

    //addLinks(root.children)

    window[listener]()
  }
  //config: m.route
  else if (arguments[0].addEventListener || arguments[0].attachEvent) {
    var element = arguments[0];
    var isInitialized = arguments[1];
    var context = arguments[2];
    var index = arguments[3];
    var ohref = context._href ?context._href: context._href=element.getAttribute('href');

    element.href = (mag.route.mode !== 'pathname' ? $location.pathname : '') + modes[mag.route.mode] + ohref;

    if (element.addEventListener) {
      element.removeEventListener("click", routeUnobtrusive.bind({}, element.href));
      element.addEventListener("click", routeUnobtrusive.bind({}, element.href))
    } else {
      element.detachEvent("onclick", routeUnobtrusive.bind({}, element.href));
      element.attachEvent("onclick", routeUnobtrusive.bind({}, element.href))
    }
  }
  //m.route(route, params, shouldReplaceHistoryEntry)
  else if (type.call(arguments[0]) === STRING) {
    var oldRoute = currentRoute;
    currentRoute = arguments[0];
    var args = arguments[1] || {}
    var queryIndex = currentRoute.indexOf("?")
    var params = queryIndex > -1 ? parseQueryString(currentRoute.slice(queryIndex + 1)) : {}
    for (var i in args) params[i] = args[i]
    var querystring = buildQueryString(params)
    var currentPath = queryIndex > -1 ? currentRoute.slice(0, queryIndex) : currentRoute
    if (querystring) currentRoute = currentPath + (currentPath.indexOf("?") === -1 ? "?" : "&") + querystring;

    var shouldReplaceHistoryEntry = (arguments.length === 3 ? arguments[2] : arguments[1]) === true || oldRoute === arguments[0];

    if (window.history.pushState) {
      //console.log('history', shouldReplaceHistoryEntry, arguments.length, oldRoute, arguments[0], modes[mag.route.mode] + currentRoute)

      computePreRedrawHook = setScroll
      computePostRedrawHook = function() {
        window.history[shouldReplaceHistoryEntry ? "replaceState" : "pushState"](null, $document.title, modes[mag.route.mode] + currentRoute);
      };
      redirect(modes[mag.route.mode] + currentRoute)

      // should be run in didupdate
      computePostRedrawHook()
    } else {
      $location[mag.route.mode] = currentRoute
      redirect(modes[mag.route.mode] + currentRoute)
    }
  }
};
mag.route.param = function(key) {
  if (!routeParams) throw new Error("You must call m.route(element, defaultRoute, routes) before calling m.route.param()")
  return routeParams[key]
};
mag.route.mode = "search";

function normalizeRoute(route) {
  return route.slice(modes[mag.route.mode].length)
}

// function addLinks(items) {
//   //var items = document.querySelectorAll('.link')
//   for (var i = items.length; i--;) {

//     var element = items[i]
//     if (element.children) addLinks(element.children)
//     if (!element.hasAttribute('href') || element.getAttribute('config') != 'route') continue

//     element.href = (mag.route.mode !== 'pathname' ? $location.pathname : '') + modes[mag.route.mode] + element.getAttribute('href');

//     //console.log('href', element.href)
//     element.addEventListener("click", routeUnobtrusive)
//   }
// }

function routeByValue(root, router, path) {
  routeParams = {};

  var queryStart = path.indexOf("?");
  if (queryStart !== -1) {
    routeParams = parseQueryString(path.substr(queryStart + 1, path.length));
    path = path.substr(0, queryStart)
  }

  // Get all routes and check if there's
  // an exact match for the current path
  var keys = Object.keys(router);
  var index = keys.indexOf(path);
  if (index !== -1) {
    router[keys[index]]()
    //console.log(path, root, routeParams)
    return true;
  }

  for (var route in router) {
    if (route === path) {
      router[route]()
      //console.log('MOUNT2!!!', root, routeParams)
      return true
    }

    var matcher = new RegExp("^" + route.replace(/:[^\/]+?\.{3}/g, "(.*?)").replace(/:[^\/]+/g, "([^\\/]+)") + "\/?$");

    if (matcher.test(path)) {
      path.replace(matcher, function() {
        var keys = route.match(/:[^\/]+/g) || [];
        var values = [].slice.call(arguments, 1, -2);
        for (var i = 0, len = keys.length; i < len; i++) routeParams[keys[i].replace(/:|\./g, "")] = decodeURIComponent(values[i])
        router[route]()
        //console.log(path, root, routeParams)
      });
      return true
    }
  }
}

function routeUnobtrusive(href, e) {

  e = e || event;
  if (e.ctrlKey || e.metaKey || e.which === 2) return;
  if (e.preventDefault) e.preventDefault();
  else e.returnValue = false;
  var currentTarget = e.currentTarget || e.srcElement;
  var args = mag.route.mode === "pathname" && currentTarget.search ? parseQueryString(currentTarget.search.slice(1)) : {};
  while (currentTarget && currentTarget.nodeName.toUpperCase() != "A") {
    currentTarget = currentTarget.parentNode
  }

  mag.route(currentTarget[mag.route.mode].slice(modes[mag.route.mode].length), args)
  if (currentTarget.href != href) {
    //WHAT?
    //currentTarget.href = href
  }

}

function setScroll() {
  if (mag.route.mode != "hash" && $location.hash) $location.hash = $location.hash;
  else window.scrollTo(0, 0)
}

function buildQueryString(object, prefix) {
  var duplicates = {}
  var str = []
  for (var prop in object) {
    var key = prefix ? prefix + "[" + prop + "]" : prop
    var value = object[prop]
    var valueType = type.call(value)
    var pair = (value === null) ? encodeURIComponent(key) :
      valueType === OBJECT ? buildQueryString(value, key) :
      valueType === ARRAY ? value.reduce(function(memo, item) {
        if (!duplicates[key]) duplicates[key] = {}
        if (!duplicates[key][item]) {
          duplicates[key][item] = true
          return memo.concat(encodeURIComponent(key) + "=" + encodeURIComponent(item))
        }
        return memo
      }, []).join("&") :
      encodeURIComponent(key) + "=" + encodeURIComponent(value)
    if (value !== undefined) str.push(pair)
  }
  return str.join("&")
}

function parseQueryString(str) {
  if (str.charAt(0) === "?") str = str.substring(1);

  var pairs = str.split("&"),
    params = {};
  for (var i = 0, len = pairs.length; i < len; i++) {
    var pair = pairs[i].split("=");
    var key = decodeURIComponent(pair[0])
    var value = pair.length == 2 ? decodeURIComponent(pair[1]) : null
    if (params[key] != null) {
      if (type.call(params[key]) !== ARRAY) params[key] = [params[key]]
      params[key].push(value)
    } else params[key] = value
  }
  return params
}
mag.route.buildQueryString = buildQueryString
mag.route.parseQueryString = parseQueryString

/*
//http://jsbin.com/magimoyodi/edit

        {{country.name}}

 */

/*
var links = [{
  text: 'Home',
  link: '/'
}, {
  text: 'Login',
  link: '/userid1232'
}]

mag.module('app', {
  controller: function(props) {},
  view: function(state, props) {
    route(state, props.links)

  }
}, {
  links: links
})

function route(data, links) {
  mag.route(data.app, "/", {
    "/": function() {
      console.log('test')
      data.countries = links.map(function(item) {
        return {
          a: {
            _config: mag.route,
            _href: item.link,
            _text: item.text
          }
        }
      })

      data.content = 'HOME'
    },
    "/:login": function() {
      data.content = 'LOGIN - ' + mag.route.param('login')
    }
  });

}
*/
// mag.route.call({},document.querySelector('a:first-child'),true,{},0)
// mag.route.call({},document.querySelector('a:nth-child(2)'),true,{},0)
<|endoftext|>