<commit_before>$(function() {
  // preview watch
  var originalContent = $('#form-body').val();
  var prevContent = "";
  var watchTimer = setInterval(function() {
    var content = $('#form-body').val();
    if (prevContent != content) {
      var renderer = new Crowi.renderer($('#form-body').val(), $('#preview-body'));
      renderer.render();

      prevContent = content;
    }
  }, 500);

  var getCurrentLine = function(event) {
    var $target = $(event.target);

    var text = $target.val();
    var pos = $target.selection('getPos');
    if (text === null || pos.start !== pos.end) {
      return null;
    }

    var startPos = text.lastIndexOf("\n", pos.start - 1) + 1;
    var endPos = text.indexOf("\n", pos.start);
    if (endPos === -1) {
      endPos = text.length;
    }

    return {
      text: text.slice(startPos, endPos),
      start: startPos,
      end: endPos,
      caret: pos.start,
      endOfLine: !$.trim(text.slice(pos.start, endPos))
    };
  };

  var getPrevLine = function(event) {
    var $target = $(event.target);
    var currentLine = getCurrentLine(event);
    var text = $target.val().slice(0, currentLine.start);
    var startPos = text.lastIndexOf("\n", currentLine.start - 2) + 1;
    var endPos = currentLine.start;

    return {
      text: text.slice(startPos, endPos),
      start: startPos,
      end: endPos
    };
  };

  var handleTabKey = function(event) {
    event.preventDefault();

    var $target = $(event.target);
    var currentLine = getCurrentLine(event);
    var text = $target.val();
    var pos = $target.selection('getPos');

    if (currentLine) {
      $target.selection('setPos', {start: currentLine.start, end: (currentLine.end - 1)});
    }

    if (event.shiftKey === true) {
      if (currentLine && currentLine.text.charAt(0) === '|') {
        // prev cell in table
        var newPos = text.lastIndexOf('|', pos.start - 1);
        if (newPos > 0) {
          $target.selection('setPos', {start: newPos - 1, end: newPos - 1});
        }
      } else {
        // re indent
        var reindentedText = $target.selection().replace(/^ {1,4}/gm, '');
        var reindentedCount = $target.selection().length - reindentedText.length;
        $target.selection('replace', {text: reindentedText, mode: 'before'});
        if (currentLine) {
          $target.selection('setPos', {start: pos.start - reindentedCount, end: pos.start - reindentedCount});
        }
      }
    } else {
      if (currentLine && currentLine.text.charAt(0) === '|') {
        // next cell in table
        var newPos = text.indexOf('|', pos.start + 1);
        if (newPos < 0 || newPos === text.lastIndexOf('|', currentLine.end - 1)) {
          $target.selection('setPos', {start: currentLine.end, end: currentLine.end});
        } else {
          $target.selection('setPos', {start: newPos + 2, end: newPos + 2});
        }
      } else {
        // indent
        $target.selection('replace', {
          text: '    ' + $target.selection().split("\n").join("\n    "),
          mode: 'before'
        });
        if (currentLine) {
          $target.selection('setPos', {start: pos.start + 4, end: pos.start + 4});
        }
      }
    }

    $target.trigger('input');
  };

  var handleEnterKey = function(event) {
    if (event.metaKey || event.ctrlKey || event.shiftKey) {
      return;
    }

    var currentLine = getCurrentLine(event);
    if (!currentLine || currentLine.start === currentLine.caret) {
      return;
    }

    var $target = $(event.target);
    var match = currentLine.text.match(/^(\s*(?:-|\+|\*|\d+\.) (?:\[(?:x| )\] )?)\s*\S/);
    if (match) {
      // smart indent with list
      if (currentLine.text.match(/^(\s*(?:-|\+|\*|\d+\.) (?:\[(?:x| )\] ))\s*$/)) {
        // empty task list
        $target.selection('setPos', {start: currentLine.start, end: (currentLine.end - 1)});
        return;
      }
      event.preventDefault();
      var listMark = match[1].replace(/\[x\]/, '[ ]');
      var listMarkMatch = listMark.match(/^(\s*)(\d+)\./);
      if (listMarkMatch) {
        var indent = listMarkMatch[1];
        var num = parseInt(listMarkMatch[2]);
        if (num !== 1) {
          listMark = listMark.return(/\s*\d+/, indent + (num +1));
        }
      }
      $target.selection('insert', {text: "\n" + listMark, mode: 'before'});
    } else if (currentLine.text.match(/^(\s*(?:-|\+|\*|\d+\.) )/)) {
      // remove list
      $target.selection('setPos', {start: currentLine.start, end: currentLine.end});
    } else if (currentLine.text.match(/^.*\|\s*$/)) {
      // new row for table
      if (currentLine.text.match(/^[\|\s]+$/)) {
        $target.selection('setPos', {start: currentLine.start, end: currentLine.end});
        return;
      }
      if (!currentLine.endOfLine) {
        return;
      }
      event.preventDefault();
      var row = [];
      var cellbarMatch = currentLine.text.match(/\|/g);
      for (var i = 0; i < cellbarMatch.length; i++) {
        row.push('|');
      }
      var prevLine = getPrevLine(event);
      if (!prevLine || (!currentLine.text.match(/---/) && !prevLine.text.match(/\|/g))) {
        $target.selection('insert', {text: "\n" + row.join(' --- ') + "\n" + row.join('  '), mode: 'before'});
        $target.selection('setPos', {start: currentLine.caret + 6 * row.length - 1, end: currentLine.caret + 6 * row.length - 1});
      } else {
        $target.selection('insert', {text: "\n" + row.join('  '), mode: 'before'});
        $target.selection('setPos', {start: currentLine.caret + 3, end: currentLine.caret + 3});
      }
    }

    $target.trigger('input');
  };

  var handleEscapeKey = function(event) {
    event.preventDefault();
    var $target = $(event.target);
    $target.blur();
  };

  var handleSpaceKey = function(event) {
    // keybind: alt + shift + space
    if (!(event.shiftKey && event.altKey)) {
      return;
    }
    var currentLine = getCurrentLine(event);
    if (!currentLine) {
      return;
    }

    var $target = $(event.target);
    var match = currentLine.text.match(/^(\s*)(-|\+|\*|\d+\.) (?:\[(x| )\] )(.*)/);
    if (match) {
      event.preventDefault();
      var checkMark = (match[3] == ' ') ? 'x' : ' ';
      var replaceTo = match[1] + match[2] + ' [' + checkMark + '] ' + match[4];
      $target.selection('setPos', {start: currentLine.start, end: currentLine.end});
      $target.selection('replace', {text: replaceTo, mode: 'keep'});
      $target.selection('setPos', {start: currentLine.caret, end: currentLine.caret});
      $target.trigger('input');
    }
  };

  // markdown helper inspired by 'esarea'.
  // see: https://github.com/fukayatsu/esarea
  $('textarea#form-body').on('keydown', function(event) {
    switch (event.which || event.keyCode) {
      case 9:
        handleTabKey(event);
        break;
      case 13:
        handleEnterKey(event);
        break;
      case 27:
        handleEscapeKey(event);
        break;
      case 32:
        handleSpaceKey(event);
        break;
      default:
    }
  });

  var handlePasteEvent = function(event) {
    var currentLine = getCurrentLine(event);
    var $target = $(event.target);
    var pasteText = event.clipboardData.getData('text');

    var match = currentLine.text.match(/^(\s*(?:>|\-|\+|\*|\d+\.) (?:\[(?:x| )\] )?)/);
    if (match) {
      if (pasteText.match(/\n/)) {
        pasteText = pasteText.replace(/(\n)/, "$1" + match[1]); // + ' ');
      }
    }

    $target.selection('insert', {text: pasteText, mode: 'after'});

    var newPos = currentLine.end + pasteText.length;
    $target.selection('setPos', {start: newPos, end: newPos});
  };

  document.getElementById('form-body').addEventListener('paste', function(event) {
    event.preventDefault();
    handlePasteEvent(event);
  });

  var unbindInlineAttachment = function($form) {
    $form.unbind('.inlineattach');
  };
  var bindInlineAttachment = function($form, attachmentOption) {
    var $this = $form;
    var editor = createEditorInstance($form);
    var inlineattach = new inlineAttachment(attachmentOption, editor);
    $form.bind({
      'paste.inlineattach': function(e) {
        inlineattach.onPaste(e.originalEvent);
      },
      'drop.inlineattach': function(e) {
        e.stopPropagation();
        e.preventDefault();
        inlineattach.onDrop(e.originalEvent);
      },
      'dragenter.inlineattach dragover.inlineattach': function(e) {
        e.stopPropagation();
        e.preventDefault();
      }
    });
  };
  var createEditorInstance = function($form) {
    var $this = $form;

    return {
      getValue: function() {
        return $this.val();
      },
      insertValue: function(val) {
        inlineAttachment.util.insertTextAtCursor($this[0], val);
      },
      setValue: function(val) {
        $this.val(val);
      }
    };
  };

  var $inputForm = $('form.uploadable textarea#form-body');
  if ($inputForm.length > 0) {
    var pageId = $('#content-main').data('page-id') || 0;
    var attachmentOption = {
      uploadUrl: '/_api/attachment/page/' + pageId,
      extraParams: {
        path: location.pathname
      },
      progressText: '(Uploading file...)',
      urlText: "\n![file]({filename})\n"
    };

    attachmentOption.onFileUploadResponse = function(res) {
      var result = JSON.parse(res.response);

      if (result.status && result.pageCreated) {
        var page = result.page,
            pageId = page._id;

        $('#content-main').data('page-id', page._id);
        $('#page-form [name="pageForm[currentRevision]"]').val(page.revision._id)

        unbindInlineAttachment($inputForm);

        attachmentOption.uploadUrl = '/_api/attachment/page/' + pageId,
        bindInlineAttachment($inputForm, attachmentOption);
      }
      return true;
    };

    bindInlineAttachment($inputForm, attachmentOption);

    $('textarea#form-body').on('dragenter dragover', function() {
      $(this).addClass('dragover');
    });
    $('textarea#form-body').on('drop dragleave dragend', function() {
      $(this).removeClass('dragover');
    });
  }
});
<commit_msg>Fixed regex bug.<commit_after>$(function() {
  // preview watch
  var originalContent = $('#form-body').val();
  var prevContent = "";
  var watchTimer = setInterval(function() {
    var content = $('#form-body').val();
    if (prevContent != content) {
      var renderer = new Crowi.renderer($('#form-body').val(), $('#preview-body'));
      renderer.render();

      prevContent = content;
    }
  }, 500);

  var getCurrentLine = function(event) {
    var $target = $(event.target);

    var text = $target.val();
    var pos = $target.selection('getPos');
    if (text === null || pos.start !== pos.end) {
      return null;
    }

    var startPos = text.lastIndexOf("\n", pos.start - 1) + 1;
    var endPos = text.indexOf("\n", pos.start);
    if (endPos === -1) {
      endPos = text.length;
    }

    return {
      text: text.slice(startPos, endPos),
      start: startPos,
      end: endPos,
      caret: pos.start,
      endOfLine: !$.trim(text.slice(pos.start, endPos))
    };
  };

  var getPrevLine = function(event) {
    var $target = $(event.target);
    var currentLine = getCurrentLine(event);
    var text = $target.val().slice(0, currentLine.start);
    var startPos = text.lastIndexOf("\n", currentLine.start - 2) + 1;
    var endPos = currentLine.start;

    return {
      text: text.slice(startPos, endPos),
      start: startPos,
      end: endPos
    };
  };

  var handleTabKey = function(event) {
    event.preventDefault();

    var $target = $(event.target);
    var currentLine = getCurrentLine(event);
    var text = $target.val();
    var pos = $target.selection('getPos');

    if (currentLine) {
      $target.selection('setPos', {start: currentLine.start, end: (currentLine.end - 1)});
    }

    if (event.shiftKey === true) {
      if (currentLine && currentLine.text.charAt(0) === '|') {
        // prev cell in table
        var newPos = text.lastIndexOf('|', pos.start - 1);
        if (newPos > 0) {
          $target.selection('setPos', {start: newPos - 1, end: newPos - 1});
        }
      } else {
        // re indent
        var reindentedText = $target.selection().replace(/^ {1,4}/gm, '');
        var reindentedCount = $target.selection().length - reindentedText.length;
        $target.selection('replace', {text: reindentedText, mode: 'before'});
        if (currentLine) {
          $target.selection('setPos', {start: pos.start - reindentedCount, end: pos.start - reindentedCount});
        }
      }
    } else {
      if (currentLine && currentLine.text.charAt(0) === '|') {
        // next cell in table
        var newPos = text.indexOf('|', pos.start + 1);
        if (newPos < 0 || newPos === text.lastIndexOf('|', currentLine.end - 1)) {
          $target.selection('setPos', {start: currentLine.end, end: currentLine.end});
        } else {
          $target.selection('setPos', {start: newPos + 2, end: newPos + 2});
        }
      } else {
        // indent
        $target.selection('replace', {
          text: '    ' + $target.selection().split("\n").join("\n    "),
          mode: 'before'
        });
        if (currentLine) {
          $target.selection('setPos', {start: pos.start + 4, end: pos.start + 4});
        }
      }
    }

    $target.trigger('input');
  };

  var handleEnterKey = function(event) {
    if (event.metaKey || event.ctrlKey || event.shiftKey) {
      return;
    }

    var currentLine = getCurrentLine(event);
    if (!currentLine || currentLine.start === currentLine.caret) {
      return;
    }

    var $target = $(event.target);
    var match = currentLine.text.match(/^(\s*(?:-|\+|\*|\d+\.) (?:\[(?:x| )\] )?)\s*\S/);
    if (match) {
      // smart indent with list
      if (currentLine.text.match(/^(\s*(?:-|\+|\*|\d+\.) (?:\[(?:x| )\] ))\s*$/)) {
        // empty task list
        $target.selection('setPos', {start: currentLine.start, end: (currentLine.end - 1)});
        return;
      }
      event.preventDefault();
      var listMark = match[1].replace(/\[x\]/, '[ ]');
      var listMarkMatch = listMark.match(/^(\s*)(\d+)\./);
      if (listMarkMatch) {
        var indent = listMarkMatch[1];
        var num = parseInt(listMarkMatch[2]);
        if (num !== 1) {
          listMark = listMark.return(/\s*\d+/, indent + (num +1));
        }
      }
      $target.selection('insert', {text: "\n" + listMark, mode: 'before'});
    } else if (currentLine.text.match(/^(\s*(?:-|\+|\*|\d+\.) )/)) {
      // remove list
      $target.selection('setPos', {start: currentLine.start, end: currentLine.end});
    } else if (currentLine.text.match(/^.*\|\s*$/)) {
      // new row for table
      if (currentLine.text.match(/^[\|\s]+$/)) {
        $target.selection('setPos', {start: currentLine.start, end: currentLine.end});
        return;
      }
      if (!currentLine.endOfLine) {
        return;
      }
      event.preventDefault();
      var row = [];
      var cellbarMatch = currentLine.text.match(/\|/g);
      for (var i = 0; i < cellbarMatch.length; i++) {
        row.push('|');
      }
      var prevLine = getPrevLine(event);
      if (!prevLine || (!currentLine.text.match(/---/) && !prevLine.text.match(/\|/g))) {
        $target.selection('insert', {text: "\n" + row.join(' --- ') + "\n" + row.join('  '), mode: 'before'});
        $target.selection('setPos', {start: currentLine.caret + 6 * row.length - 1, end: currentLine.caret + 6 * row.length - 1});
      } else {
        $target.selection('insert', {text: "\n" + row.join('  '), mode: 'before'});
        $target.selection('setPos', {start: currentLine.caret + 3, end: currentLine.caret + 3});
      }
    }

    $target.trigger('input');
  };

  var handleEscapeKey = function(event) {
    event.preventDefault();
    var $target = $(event.target);
    $target.blur();
  };

  var handleSpaceKey = function(event) {
    // keybind: alt + shift + space
    if (!(event.shiftKey && event.altKey)) {
      return;
    }
    var currentLine = getCurrentLine(event);
    if (!currentLine) {
      return;
    }

    var $target = $(event.target);
    var match = currentLine.text.match(/^(\s*)(-|\+|\*|\d+\.) (?:\[(x| )\] )(.*)/);
    if (match) {
      event.preventDefault();
      var checkMark = (match[3] == ' ') ? 'x' : ' ';
      var replaceTo = match[1] + match[2] + ' [' + checkMark + '] ' + match[4];
      $target.selection('setPos', {start: currentLine.start, end: currentLine.end});
      $target.selection('replace', {text: replaceTo, mode: 'keep'});
      $target.selection('setPos', {start: currentLine.caret, end: currentLine.caret});
      $target.trigger('input');
    }
  };

  // markdown helper inspired by 'esarea'.
  // see: https://github.com/fukayatsu/esarea
  $('textarea#form-body').on('keydown', function(event) {
    switch (event.which || event.keyCode) {
      case 9:
        handleTabKey(event);
        break;
      case 13:
        handleEnterKey(event);
        break;
      case 27:
        handleEscapeKey(event);
        break;
      case 32:
        handleSpaceKey(event);
        break;
      default:
    }
  });

  var handlePasteEvent = function(event) {
    var currentLine = getCurrentLine(event);
    var $target = $(event.target);
    var pasteText = event.clipboardData.getData('text');

    var match = currentLine.text.match(/^(\s*(?:>|\-|\+|\*|\d+\.) (?:\[(?:x| )\] )?)/);
    if (match) {
      if (pasteText.match(/\n/)) {
        pasteText = pasteText.replace(/(\n)/g, "$1" + match[1]); // + ' ');
      }
    }

    $target.selection('insert', {text: pasteText, mode: 'after'});

    var newPos = currentLine.end + pasteText.length;
    $target.selection('setPos', {start: newPos, end: newPos});
  };

  document.getElementById('form-body').addEventListener('paste', function(event) {
    event.preventDefault();
    handlePasteEvent(event);
  });

  var unbindInlineAttachment = function($form) {
    $form.unbind('.inlineattach');
  };
  var bindInlineAttachment = function($form, attachmentOption) {
    var $this = $form;
    var editor = createEditorInstance($form);
    var inlineattach = new inlineAttachment(attachmentOption, editor);
    $form.bind({
      'paste.inlineattach': function(e) {
        inlineattach.onPaste(e.originalEvent);
      },
      'drop.inlineattach': function(e) {
        e.stopPropagation();
        e.preventDefault();
        inlineattach.onDrop(e.originalEvent);
      },
      'dragenter.inlineattach dragover.inlineattach': function(e) {
        e.stopPropagation();
        e.preventDefault();
      }
    });
  };
  var createEditorInstance = function($form) {
    var $this = $form;

    return {
      getValue: function() {
        return $this.val();
      },
      insertValue: function(val) {
        inlineAttachment.util.insertTextAtCursor($this[0], val);
      },
      setValue: function(val) {
        $this.val(val);
      }
    };
  };

  var $inputForm = $('form.uploadable textarea#form-body');
  if ($inputForm.length > 0) {
    var pageId = $('#content-main').data('page-id') || 0;
    var attachmentOption = {
      uploadUrl: '/_api/attachment/page/' + pageId,
      extraParams: {
        path: location.pathname
      },
      progressText: '(Uploading file...)',
      urlText: "\n![file]({filename})\n"
    };

    attachmentOption.onFileUploadResponse = function(res) {
      var result = JSON.parse(res.response);

      if (result.status && result.pageCreated) {
        var page = result.page,
            pageId = page._id;

        $('#content-main').data('page-id', page._id);
        $('#page-form [name="pageForm[currentRevision]"]').val(page.revision._id)

        unbindInlineAttachment($inputForm);

        attachmentOption.uploadUrl = '/_api/attachment/page/' + pageId,
        bindInlineAttachment($inputForm, attachmentOption);
      }
      return true;
    };

    bindInlineAttachment($inputForm, attachmentOption);

    $('textarea#form-body').on('dragenter dragover', function() {
      $(this).addClass('dragover');
    });
    $('textarea#form-body').on('drop dragleave dragend', function() {
      $(this).removeClass('dragover');
    });
  }
});
<|endoftext|>


<commit_before>"use strict";

const chalk = require("chalk");
const columnify = require("columnify");

const Command = require("../Command");
const output = require("../utils/output");

exports.handler = function handler(argv) {
  // eslint-disable-next-line no-use-before-define
  return new LsCommand(argv);
};

exports.command = "ls";

exports.describe = "List all public packages";

exports.builder = {
  json: {
    describe: "Show information in JSON format",
    group: "Command Options:",
    type: "boolean",
    default: undefined,
  },
};

class LsCommand extends Command {
  get requiresGit() {
    return false;
  }

  get defaultOptions() {
    return Object.assign({}, super.defaultOptions, {
      json: false,
    });
  }

  initialize(callback) {
    this.resultList = this.filteredPackages.map(pkg => ({
      name: pkg.name,
      version: pkg.version,
      private: pkg.private,
    }));

    callback(null, true);
  }

  execute(callback) {
    let result;

    if (this.options.json) {
      result = this.formatJSON();
    } else {
      result = this.formatColumns();
    }

    output(result);

    callback(null, true);
  }

  formatJSON() {
    return JSON.stringify(this.resultList, null, 2);
  }

  formatColumns() {
    const formattedResults = this.resultList.map(result => {
      const formatted = {
        name: result.name,
      };

      if (result.version) {
        formatted.version = chalk.grey(`v${result.version}`);
      } else {
        formatted.version = chalk.yellow("MISSING");
      }

      if (result.private) {
        formatted.private = `(${chalk.red("private")})`;
      }

      return formatted;
    });

    return columnify(formattedResults, {
      showHeaders: false,
      config: {
        version: {
          align: "right",
        },
      },
    });
  }
}
<commit_msg>fix(LsCommand): add 'list' alias<commit_after>"use strict";

const chalk = require("chalk");
const columnify = require("columnify");

const Command = require("../Command");
const output = require("../utils/output");

exports.handler = function handler(argv) {
  // eslint-disable-next-line no-use-before-define
  return new LsCommand(argv);
};

exports.command = "ls";

exports.aliases = ["list"];

exports.describe = "List local packages";

exports.builder = {
  json: {
    describe: "Show information in JSON format",
    group: "Command Options:",
    type: "boolean",
    default: undefined,
  },
};

class LsCommand extends Command {
  get requiresGit() {
    return false;
  }

  get defaultOptions() {
    return Object.assign({}, super.defaultOptions, {
      json: false,
    });
  }

  initialize(callback) {
    this.resultList = this.filteredPackages.map(pkg => ({
      name: pkg.name,
      version: pkg.version,
      private: pkg.private,
    }));

    callback(null, true);
  }

  execute(callback) {
    let result;

    if (this.options.json) {
      result = this.formatJSON();
    } else {
      result = this.formatColumns();
    }

    output(result);

    callback(null, true);
  }

  formatJSON() {
    return JSON.stringify(this.resultList, null, 2);
  }

  formatColumns() {
    const formattedResults = this.resultList.map(result => {
      const formatted = {
        name: result.name,
      };

      if (result.version) {
        formatted.version = chalk.grey(`v${result.version}`);
      } else {
        formatted.version = chalk.yellow("MISSING");
      }

      if (result.private) {
        formatted.private = `(${chalk.red("private")})`;
      }

      return formatted;
    });

    return columnify(formattedResults, {
      showHeaders: false,
      config: {
        version: {
          align: "right",
        },
      },
    });
  }
}
<|endoftext|>


<commit_before>var d3 = require("d3");
var Model = require("model-js");
var ChiasmDataset = require("chiasm-dataset");
var getColumnMetadata = ChiasmDataset.getColumnMetadata;

// TODO split up this file.

function marginConvention(my, svg){
  var g = svg.append("g");

  my.addPublicProperty("margin", {top: 20, right: 20, bottom: 50, left: 60});

  my.when(["box", "margin"], function (box, margin){
    my.width = box.width - margin.left - margin.right;
    my.height = box.height - margin.top - margin.bottom;
    g.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
  });

  return g;
}

function marginEditor(my, svg){

  // The width of the handles.
  my.addPublicProperty("marginEditorWidth", 15);

  // The color of the handles on hover.
  my.addPublicProperty("marginEditorFill", "rgba(0, 0, 0, 0.2)");

  var data = [ "left", "right", "top", "bottom" ];

  var handles = svg.selectAll(".margin-handle").data(data);
  handles.enter().append("rect")
    .attr("class", "margin-handle")
    .style("fill", "none")
    .style("pointer-events", "all")

  var drag = d3.behavior.drag().on("drag", function (d) {

    // Mutate the margin object.
    my.margin[d] = {
      left:    d3.event.x,
      right:  -d3.event.x,
      top:     d3.event.y,
      bottom: -d3.event.y,
    }[d];

    // Notify model-js that the margin object has been mutated.
    my.margin = my.margin;

    // Hide the handles during drag.
    handles.style("fill", "none");
  });

  handles.call(drag);

  // Fill in the handles on hover.
  my.when("marginEditorFill", function (marginEditorFill){
    handles
      .on("mouseenter", function (){
        d3.select(this).style("fill", marginEditorFill);
      })
      .on("mouseout", function (){
        d3.select(this).style("fill", "none");
      });
  });

  // Update the drag origin function based on the margin.
  my.when("margin", function (margin){
    var origins = {
      left:   { x: margin.left,   y: 0 },
      right:  { x: -margin.right, y: 0 },
      top:    { x: 0,             y: margin.top },
      bottom: { x: 0,             y: -margin.bottom },
    };
    drag.origin(function(d) {
      return origins[d];
    });
  });

  // Render the handles
  my.when(["margin", "marginEditorWidth", "width", "height"],
      function (margin, marginEditorWidth, width, height){

    var x = {
      left:   margin.left         - marginEditorWidth / 2,
      right:  margin.left + width - marginEditorWidth / 2,
      top:    margin.left,
      bottom: margin.left
    };

    var y = {
      left:   margin.top,
      right:  margin.top,
      top:    margin.top          - marginEditorWidth / 2,
      bottom: margin.top + height - marginEditorWidth / 2
    };

    var w = {
      left:   marginEditorWidth,
      right:  marginEditorWidth,
      top:    width,
      bottom: width
    };

    var h = {
      left:   height,
      right:  height,
      top:    marginEditorWidth,
      bottom: marginEditorWidth
    };

    var cursor = {
      left:   "ew-resize",
      right:  "ew-resize",
      top:    "ns-resize",
      bottom: "ns-resize"
    };

    handles
      .attr("x",       function (d){ return x[d]; })
      .attr("y",       function (d){ return y[d]; })
      .attr("width",   function (d){ return w[d]; })
      .attr("height",  function (d){ return h[d]; })
      .style("cursor", function (d){ return cursor[d]; })

      // Rounded corners.
      .attr("rx", function (d){ return marginEditorWidth / 2; })
      .attr("ry", function (d){ return marginEditorWidth / 2; });

  });
}

function scale(my, prefix, initialScaleType){

  var scaleName    = prefix + "Scale";
  var scaleDomain  = prefix + "ScaleDomain";
  var scaleRange   = prefix + "ScaleRange";
  var scalePadding = prefix + "ScaleRangePadding"
  var scaleType    = prefix + "ScaleType";

  var columnName     = prefix + "Column";
  var columnAccessor = prefix + "Accessor";
  var scaled         = prefix + "Scaled";

  my.addPublicProperty(columnName, Model.None);

  // TODO this feels like it should be elsewhere.
  if(prefix === "x"){
    my.when("width", function (width){
      my[scaleRange] = [0, width];
    });
  } else if(prefix === "y"){
    my.when("height", function (height){
      my[scaleRange] = [height, 0];
    });
  }

  var scaleTypes = {
    linear: function (my){
      var myScale = d3.scale.linear();
      return my.when([scaleDomain, scaleRange], function (domain, range){
        if(domain !== Model.None && range !== Model.None){
          my[scaleName] = myScale.domain(domain).range(range);
        }
      });
    },
    ordinal: function (my){
      var myScale = d3.scale.ordinal();
      return my.when([scaleDomain, scaleRange, scalePadding], function (domain, range, padding){
        if(domain !== Model.None && range !== Model.None){
          my[scaleName] = myScale.domain(domain).rangeBands(range, padding);
        }
      });
    },
    time: function (my){
      var myScale = d3.time.scale();
      return my.when([scaleDomain, scaleRange], function (domain, range){
        if(domain !== Model.None && range !== Model.None){
          my[scaleName] = myScale.domain(domain).range(range);
        }
      });
    }
  };

  my.addPublicProperty(scaleDomain, Model.None);
  my.addPublicProperty(scaleType, initialScaleType ? initialScaleType : Model.None )

  // This property is relevant only for ordinal scales.
  my.addPublicProperty(scalePadding, 0.1);

  var oldListener;
  my.when(scaleType, function (type){

    if(type !== Model.None){

      // TODO add tests for this line.
      if(oldListener){ my.cancel(oldListener); }

      oldListener = scaleTypes[type](my);
    }
  });

  my.when(columnName, function (column){
    my[columnAccessor] = function (d){ return d[column]; };
  });

  my.when([scaleName, columnName], function (scale, column){
    my[scaled] = function (d){ return scale(d[column]); };
  });
}

function autoScaleType(my, prefix){

  var columnName = prefix + "Column";
  var columnAccessor = prefix + "Accessor";
  var scaleDomain  = prefix + "ScaleDomain";
  var scaleType = prefix + "ScaleType";
  var columnAccessor = prefix + "Accessor";

  my.when(["dataset", columnName, columnAccessor], function (dataset, column, accessor){
    if(column !== Model.None){

      // TODO make this a model property
      var columnMetadata = getColumnMetadata(dataset, column);
      var interval = columnMetadata.interval;

      // This case deals with histogram bins.
      if(interval){
        my[scaleType] = "linear";
        my[scaleDomain] = d3.extent(dataset.data, accessor);
        my[scaleDomain][1] += interval;

      // This case deals with an ordinal domain (a string column).
      } else {
        my[scaleType] = "ordinal";
        my[scaleDomain] = dataset.data.map(accessor);
      }
    }
  });
}

function rangeBands(my, prefix){

  var scaleName = prefix + "Scale";
  var columnName = prefix + "Column";
  var rangeBand = prefix + "RangeBand";

  my.when(["dataset", scaleName, columnName], function (dataset, scale, column) {

    // TODO make this a model property
    var columnMetadata = getColumnMetadata(dataset, column);

    var interval = columnMetadata.interval;
    if(interval){
      my[rangeBand] = Math.abs( scale(interval) - scale(0) );
    } else {
      my[rangeBand] = scale.rangeBand();
    }
  });
}

function xAxis(my, g){
  var axisG = g.append("g").attr("class", "x axis");
  var axis = d3.svg.axis();

  my.addPublicProperty("xAxisTickDensity", 70);

  my.when(["xScale", "xAxisTickDensity", "width"], function (xScale, xAxisTickDensity, width){
    axis.scale(xScale).ticks(width / xAxisTickDensity)
    axisG.call(axis);
  });

  my.when("height", function (height){
    axisG.attr("transform", "translate(0," + height + ")");
  });

  return axisG;
}

function yAxis(my, g){
  var axisG = g.append("g").attr("class", "y axis");
  var axis = d3.svg.axis().orient("left");

  my.addPublicProperty("yAxisTickDensity", 30);

  my.when(["yScale", "yAxisTickDensity", "height"], function (yScale, yAxisTickDensity, height){
    axis.scale(yScale).ticks(height / yAxisTickDensity)
    axisG.call(axis);
  });

  return axisG;
}

function xAxisLabel(my, xAxisG){
  var label = xAxisG.append("text").attr("class", "x axis-label");
  my.addPublicProperty("xAxisLabelText", "X Axis Label");
  my.addPublicProperty("xAxisLabelTextOffset", 43);

  my.when("xAxisLabelText", function (xAxisLabelText){
    label.text(xAxisLabelText);
  });

  my.when("xAxisLabelTextOffset", function (xAxisLabelTextOffset){
    label.attr("y", xAxisLabelTextOffset);
  });

  my.when("width", function (width){
    label.attr("x", width / 2);
  });
}

function yAxisLabel(my, yAxisG){
  var label = yAxisG.append("text").attr("class", "y axis-label");
  my.addPublicProperty("yAxisLabelText", "Y Axis Label");
  my.addPublicProperty("yAxisLabelTextOffset", 35);

  my.when("yAxisLabelText", function (yAxisLabelText){
    label.text(yAxisLabelText);
  });

  my.when(["yAxisLabelTextOffset", "height"], function (offset, height){
    label.attr("transform", "translate(-" + offset + "," + (height / 2) + ") rotate(-90)")
  });
}

module.exports = {
  marginConvention: marginConvention,
  marginEditor: marginEditor,
  scale: scale,
  autoScaleType: autoScaleType,
  rangeBands: rangeBands,
  xAxis: xAxis,
  xAxisLabel: xAxisLabel,
  yAxis: yAxis,
  yAxisLabel: yAxisLabel
};
<commit_msg>Expose column metadata in model<commit_after>var d3 = require("d3");
var Model = require("model-js");
var ChiasmDataset = require("chiasm-dataset");
var getColumnMetadata = ChiasmDataset.getColumnMetadata;

// TODO split up this file.

function marginConvention(my, svg){
  var g = svg.append("g");

  my.addPublicProperty("margin", {top: 20, right: 20, bottom: 50, left: 60});

  my.when(["box", "margin"], function (box, margin){
    my.width = box.width - margin.left - margin.right;
    my.height = box.height - margin.top - margin.bottom;
    g.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
  });

  return g;
}

function marginEditor(my, svg){

  // The width of the handles.
  my.addPublicProperty("marginEditorWidth", 15);

  // The color of the handles on hover.
  my.addPublicProperty("marginEditorFill", "rgba(0, 0, 0, 0.2)");

  var data = [ "left", "right", "top", "bottom" ];

  var handles = svg.selectAll(".margin-handle").data(data);
  handles.enter().append("rect")
    .attr("class", "margin-handle")
    .style("fill", "none")
    .style("pointer-events", "all")

  var drag = d3.behavior.drag().on("drag", function (d) {

    // Mutate the margin object.
    my.margin[d] = {
      left:    d3.event.x,
      right:  -d3.event.x,
      top:     d3.event.y,
      bottom: -d3.event.y,
    }[d];

    // Notify model-js that the margin object has been mutated.
    my.margin = my.margin;

    // Hide the handles during drag.
    handles.style("fill", "none");
  });

  handles.call(drag);

  // Fill in the handles on hover.
  my.when("marginEditorFill", function (marginEditorFill){
    handles
      .on("mouseenter", function (){
        d3.select(this).style("fill", marginEditorFill);
      })
      .on("mouseout", function (){
        d3.select(this).style("fill", "none");
      });
  });

  // Update the drag origin function based on the margin.
  my.when("margin", function (margin){
    var origins = {
      left:   { x: margin.left,   y: 0 },
      right:  { x: -margin.right, y: 0 },
      top:    { x: 0,             y: margin.top },
      bottom: { x: 0,             y: -margin.bottom },
    };
    drag.origin(function(d) {
      return origins[d];
    });
  });

  // Render the handles
  my.when(["margin", "marginEditorWidth", "width", "height"],
      function (margin, marginEditorWidth, width, height){

    var x = {
      left:   margin.left         - marginEditorWidth / 2,
      right:  margin.left + width - marginEditorWidth / 2,
      top:    margin.left,
      bottom: margin.left
    };

    var y = {
      left:   margin.top,
      right:  margin.top,
      top:    margin.top          - marginEditorWidth / 2,
      bottom: margin.top + height - marginEditorWidth / 2
    };

    var w = {
      left:   marginEditorWidth,
      right:  marginEditorWidth,
      top:    width,
      bottom: width
    };

    var h = {
      left:   height,
      right:  height,
      top:    marginEditorWidth,
      bottom: marginEditorWidth
    };

    var cursor = {
      left:   "ew-resize",
      right:  "ew-resize",
      top:    "ns-resize",
      bottom: "ns-resize"
    };

    handles
      .attr("x",       function (d){ return x[d]; })
      .attr("y",       function (d){ return y[d]; })
      .attr("width",   function (d){ return w[d]; })
      .attr("height",  function (d){ return h[d]; })
      .style("cursor", function (d){ return cursor[d]; })

      // Rounded corners.
      .attr("rx", function (d){ return marginEditorWidth / 2; })
      .attr("ry", function (d){ return marginEditorWidth / 2; });

  });
}

function scale(my, prefix, initialScaleType){

  var scaleName    = prefix + "Scale";
  var scaleDomain  = prefix + "ScaleDomain";
  var scaleRange   = prefix + "ScaleRange";
  var scalePadding = prefix + "ScaleRangePadding"
  var scaleType    = prefix + "ScaleType";

  var columnName     = prefix + "Column";
  var columnAccessor = prefix + "Accessor";
  var scaled         = prefix + "Scaled";
  var columnMetadata = prefix + "Metadata";

  my.addPublicProperty(columnName, Model.None);

  // TODO this feels like it should be elsewhere.
  if(prefix === "x"){
    my.when("width", function (width){
      my[scaleRange] = [0, width];
    });
  } else if(prefix === "y"){
    my.when("height", function (height){
      my[scaleRange] = [height, 0];
    });
  }

  var scaleTypes = {
    linear: function (my){
      var myScale = d3.scale.linear();
      return my.when([scaleDomain, scaleRange], function (domain, range){
        if(domain !== Model.None && range !== Model.None){
          my[scaleName] = myScale.domain(domain).range(range);
        }
      });
    },
    ordinal: function (my){
      var myScale = d3.scale.ordinal();
      return my.when([scaleDomain, scaleRange, scalePadding], function (domain, range, padding){
        if(domain !== Model.None && range !== Model.None){
          my[scaleName] = myScale.domain(domain).rangeBands(range, padding);
        }
      });
    },
    time: function (my){
      var myScale = d3.time.scale();
      return my.when([scaleDomain, scaleRange], function (domain, range){
        if(domain !== Model.None && range !== Model.None){
          my[scaleName] = myScale.domain(domain).range(range);
        }
      });
    }
  };

  my.addPublicProperty(scaleDomain, Model.None);
  my.addPublicProperty(scaleType, initialScaleType ? initialScaleType : Model.None )

  // This property is relevant only for ordinal scales.
  my.addPublicProperty(scalePadding, 0.1);

  var oldListener;
  my.when(scaleType, function (type){

    if(type !== Model.None){

      // TODO add tests for this line.
      if(oldListener){ my.cancel(oldListener); }

      oldListener = scaleTypes[type](my);
    }
  });

  my.when(columnName, function (column){
    if(column !== Model.None){
      my[columnAccessor] = function (d){ return d[column]; };
    }
  });

  my.when([scaleName, columnName], function (scale, column){
    if(column !== Model.None){
      my[scaled] = function (d){ return scale(d[column]); };
    }
  });

  my.when(["dataset", columnName], function (dataset, column){
    if(column !== Model.None){
      my[columnMetadata] = getColumnMetadata(dataset, column);
    }
  });

}

function autoScaleType(my, prefix){

  var columnName = prefix + "Column";
  var columnAccessor = prefix + "Accessor";
  var scaleDomain  = prefix + "ScaleDomain";
  var scaleType = prefix + "ScaleType";
  var columnAccessor = prefix + "Accessor";
  var columnMetadata = prefix + "Metadata";

  my.when(["dataset", columnMetadata, columnAccessor], function (dataset, metadata, accessor){

    // This case deals with histogram bins.
    if(metadata.interval){
      my[scaleType] = "linear";
      my[scaleDomain] = d3.extent(dataset.data, accessor);
      my[scaleDomain][1] += metadata.interval;

    // This case deals with an ordinal domain (a string column).
    } else {
      my[scaleType] = "ordinal";
      my[scaleDomain] = dataset.data.map(accessor);
    }
  });
}

function rangeBands(my, prefix){

  var scaleName = prefix + "Scale";
  var columnMetadata = prefix + "Metadata";
  var rangeBand = prefix + "RangeBand";

  my.when([columnMetadata, scaleName], function (metadata, scale) {
    if(metadata.interval){
      my[rangeBand] = Math.abs( scale(metadata.interval) - scale(0) );
    } else {
      my[rangeBand] = scale.rangeBand();
    }
  });
}

function xAxis(my, g){
  var axisG = g.append("g").attr("class", "x axis");
  var axis = d3.svg.axis();

  my.addPublicProperty("xAxisTickDensity", 70);

  my.when(["xScale", "xAxisTickDensity", "width"], function (xScale, xAxisTickDensity, width){
    axis.scale(xScale).ticks(width / xAxisTickDensity)
    axisG.call(axis);
  });

  my.when("height", function (height){
    axisG.attr("transform", "translate(0," + height + ")");
  });

  return axisG;
}

function yAxis(my, g){
  var axisG = g.append("g").attr("class", "y axis");
  var axis = d3.svg.axis().orient("left");

  my.addPublicProperty("yAxisTickDensity", 30);

  my.when(["yScale", "yAxisTickDensity", "height"], function (yScale, yAxisTickDensity, height){
    axis.scale(yScale).ticks(height / yAxisTickDensity)
    axisG.call(axis);
  });

  return axisG;
}

function xAxisLabel(my, xAxisG){
  var label = xAxisG.append("text").attr("class", "x axis-label");
  my.addPublicProperty("xAxisLabelText", "X Axis Label");
  my.addPublicProperty("xAxisLabelTextOffset", 43);

  my.when("xAxisLabelText", function (xAxisLabelText){
    label.text(xAxisLabelText);
  });

  my.when("xAxisLabelTextOffset", function (xAxisLabelTextOffset){
    label.attr("y", xAxisLabelTextOffset);
  });

  my.when("width", function (width){
    label.attr("x", width / 2);
  });
}

function yAxisLabel(my, yAxisG){
  var label = yAxisG.append("text").attr("class", "y axis-label");
  my.addPublicProperty("yAxisLabelText", "Y Axis Label");
  my.addPublicProperty("yAxisLabelTextOffset", 35);

  my.when("yAxisLabelText", function (yAxisLabelText){
    label.text(yAxisLabelText);
  });

  my.when(["yAxisLabelTextOffset", "height"], function (offset, height){
    label.attr("transform", "translate(-" + offset + "," + (height / 2) + ") rotate(-90)")
  });
}

module.exports = {
  marginConvention: marginConvention,
  marginEditor: marginEditor,
  scale: scale,
  autoScaleType: autoScaleType,
  rangeBands: rangeBands,
  xAxis: xAxis,
  xAxisLabel: xAxisLabel,
  yAxis: yAxis,
  yAxisLabel: yAxisLabel
};
<|endoftext|>


<commit_before>import { scaleLinear } from "d3-scale";

var scaleConstructors = {
  linear: scaleLinear
  // TODO support all scales https://github.com/datavis-tech/reactive-vis/issues/26
};

export default function Scale (my, name, options){

  options = options || {};
  var type = options.type || "linear";
  var nice = options.nice || true;

  var scale = scaleConstructors[type]();

  // (name + "Domain") and (name + "Range")
  // must be defined on the model before incoking this mixin.
  // TODO throw an error if they are not defined at this point.
  // GitHub issue #25 https://github.com/datavis-tech/reactive-vis/issues/25

  my

    (name + "Scale", function(domain, range){

      scale
        .domain(domain)
        .range(range);

      // TODO test
      //if(nice){
      //  scale.nice();
      //}

      return scale;
    }, [name + "Domain", name + "Range"])

    (name + "Scaled", function(scale, accessor){
      return function (d){
        return scale(accessor(d));
      };
    }, [name + "Scale", name + "Accessor"]);
}
<commit_msg>Move issues out of comments and into GitHub issues.<commit_after>import { scaleLinear } from "d3-scale";

var scaleConstructors = {
  linear: scaleLinear
  // TODO support all scales https://github.com/datavis-tech/reactive-vis/issues/26
};

export default function Scale (my, name, options){

  options = options || {};
  var type = options.type || "linear";

  var scale = scaleConstructors[type]();

  my

    (name + "Scale", function(domain, range){
      return scale.domain(domain).range(range);
    }, [name + "Domain", name + "Range"])

    (name + "Scaled", function(scale, accessor){
      return function (d){
        return scale(accessor(d));
      };
    }, [name + "Scale", name + "Accessor"]);
}
<|endoftext|>


<commit_before>Vue.directive('focus', {
    inserted: function (el) {
        el.focus();
    }
});

Vue.component('todo-item', {
    props: ['item', 'index', 'editmode'],
    template: '\

\
      <input type="checkbox"  v-if="!(editmode.enabled && editmode.index == index)" @click="$emit(\'strike\')" :checked="item.strikeout">\

\
      <textarea class="editor" v-focus="true" @keydown.ctrl.83="$emit(\'save\')" v-model.trim="item.text" type="text"></textarea>\

\

\
        <input type="checkbox" @keydown.ctrl.83="$emit(\'save\')" :checked="item.remind_enabled">\
        Remind\

\
      <input type="date" v-model="item.remind_date" @keydown.ctrl.83="$emit(\'save\')">\
      <input type="time" v-model="item.remind_time" @keydown.ctrl.83="$emit(\'save\')">\

\

\
        <button type="button" class="btn btn-default" @click="$emit(\'save\')" title="Ctrl+S">Save</button>\
        <button type="button" class="btn btn-default" @click="$emit(\'cancel\')" title="ESC">Cancel</button>\

\

\

\
      \
      {{item.text}}\

\

\
            <button type="button" class="btn btn-default" title="Edit item" @click="$emit(\'edit\');" v-if="!(editmode.enabled && editmode.index == index)">\
                \
            </button>\
            <button type="button" class="btn btn-default" title="Delete item" @click="$emit(\'delete\')" v-if="!(editmode.enabled && editmode.index == index)">\
                \
            </button>\

\

\
      ',
    inserted: function (el) {
        // Focus the element
        el.focus()
    }
});

var vmTodoList = new Vue({
    el: '#todo-list',
    data: {
        newItemInput: '',
        todos: [],
        editMode: {
            enabled: false,
            index: 0
        }
    },
    methods: {
        addItem: function () {
            if (!this.newItemInput) return;
            this.todos.unshift({
                text: this.newItemInput,
                strikeout: false,
                remind_enabled: false,
                remind_date: '',
                remind_time: ''

            });
            this.newItemInput = '';
            this.save()
        },
        deleteItem: function (index) {
            if (confirm('Are you sure you want to delete this todo?')) {
                this.todos.splice(index, 1);
            }
        },
        strikeItem: function (index) {
            this.todos[index].strikeout = !this.todos[index].strikeout;
        },
        checkRemind: function (index) {
            this.todos[index].remind_enabled = !this.todos[index].remind_enabled;

        },
        editItem: function (index) {
            this.editMode.index = index;
            this.editMode.enabled = !this.editMode.enabled;
        },
        saveItem: function () {
            this.editMode.enabled = !this.editMode.enabled;
             this.save()
        },
        cancelChanges: function (index) {
            this.editMode.enabled = false;
            loadTodosFromSettings(index);
        },
        save: function () {
            loadTodosToSettings();
            updateAppSettings();
        }
    }

});

<commit_msg>point<commit_after>Vue.directive('focus', {
    inserted: function (el) {
        el.focus();
    }
});

Vue.component('todo-item', {
    props: ['item', 'index', 'editmode'],
    template: '\

\
      <input type="checkbox"  v-if="!(editmode.enabled && editmode.index == index)" @click="$emit(\'strike\')" :checked="item.strikeout">\

\
      <textarea class="editor" v-focus="true" v-model.trim="item.text" type="text"></textarea>\

\

\
        <input type="checkbox" :checked="item.remind_enabled">\
        Remind\

\
      <input type="date" v-model="item.remind_date">\
      <input type="time" v-model="item.remind_time">\

\

\
        <button type="button" class="btn btn-default" @click="$emit(\'save\')" title="Ctrl+S">Save</button>\
        <button type="button" class="btn btn-default" @click="$emit(\'cancel\')" title="ESC">Cancel</button>\

\

\

\
      \
      {{item.text}}\

\

\
            <button type="button" class="btn btn-default" title="Edit item" @click="$emit(\'edit\');" v-if="!(editmode.enabled && editmode.index == index)">\
                \
            </button>\
            <button type="button" class="btn btn-default" title="Delete item" @click="$emit(\'delete\')" v-if="!(editmode.enabled && editmode.index == index)">\
                \
            </button>\

\

\
      ',
    inserted: function (el) {
        // Focus the element
        el.focus()
    }
});

var vmTodoList = new Vue({
    el: '#todo-list',
    data: {
        newItemInput: '',
        todos: [],
        editMode: {
            enabled: false,
            index: 0
        }
    },
    methods: {
        addItem: function () {
            if (!this.newItemInput) return;
            this.todos.unshift({
                text: this.newItemInput,
                strikeout: false,
                remind_enabled: false,
                remind_date: '',
                remind_time: ''

            });
            this.newItemInput = '';
            this.save()
        },
        deleteItem: function (index) {
            if (confirm('Are you sure you want to delete this todo?')) {
                this.todos.splice(index, 1);
            }
        },
        strikeItem: function (index) {
            this.todos[index].strikeout = !this.todos[index].strikeout;
        },
        checkRemind: function (index) {
            this.todos[index].remind_enabled = !this.todos[index].remind_enabled;

        },
        editItem: function (index) {
            this.editMode.index = index;
            this.editMode.enabled = !this.editMode.enabled;
        },
        saveItem: function () {
            this.editMode.enabled = !this.editMode.enabled;
             this.save()
        },
        cancelChanges: function (index) {
            this.editMode.enabled = false;
            loadTodosFromSettings(index);
        },
        save: function () {
            loadTodosToSettings();
            updateAppSettings();
        }
    }

});

<|endoftext|>


<commit_before>/**
 * Selector based embedPlayer processing
 *
 * @param {Function=}
 *      callback Optional Function to be called once video interfaces
 *      are ready
 *
 */
mw.processEmbedPlayers = function( playerSelect, callback ) {
	mw.log( 'processEmbedPlayers:: playerSelector: '+ playerSelect);

	// The player id list container
	var playerIdList = [];

	// Check if the selected player set is ready if ready issue the parent callback
	var areSelectedPlayersReady = function(){
		var playersLoaded = true;
		$.each(playerIdList, function(inx, playerId){
			if( ! $( '#' + playerId ).get(0).playerReady ){
				playersLoaded = false;
				return false;
			}
		})
		if( playersLoaded ){
			if( callback )
				callback();
		}
	}

	/**
	 * Adds a player element for the embedPlayer to rewrite
	 *
	 * uses embedPlayer interface on audio / video elements uses mvPlayList
	 * interface on playlist elements
	 *
	 * Once a player interface is established the following chain of functions
	 * are called;
	 *
	 * _this.checkPlayerSources()
	 * _this.setupSourcePlayer()
	 * _this.updatePlaybackInterface()
	 * _this.selectedPlayer.load()
	 * _this.showPlayer()
	 *
	 * @param {Element}
	 *      playerElement DOM element to be swapped
	 * @param {Object}
	 *      [Optional] attributes Extra attributes to apply to the player
	 *      interface
	 */
	var addPlayerElement = function( playerElement ) {
		var _this = this;
		mw.log('EmbedPlayer:: addElement:: ' + playerElement.id );

		// Be sure to "stop" the target ( Firefox 3x keeps playing
		// the video even though its been removed from the DOM )
		if( playerElement.pause ){
			playerElement.pause();
		}

		// Allow modules to override the wait for metadata flag:
		$( mw ).trigger( 'checkPlayerWaitForMetaData', playerElement );

		// DOM *could* load height, width and duration eventually, in some browsers
		// By default, don't bother waiting for this.
		var waitForMeta = false;

		// if a plugin has told us not to waitForMeta, don't
		if ( playerElement.waitForMeta !== false ) {
			// Check if we should wait for metadata, after all
			waitForMeta = waitForMetaCheck( playerElement );
		}

		var ranPlayerSwapFlag = false;

		// Local callback to runPlayer swap once playerElement has metadata
		function runPlayerSwap() {
			// Don't run player swap twice
			if( ranPlayerSwapFlag ){
				return ;
			}
			ranPlayerSwapFlag = true;
			mw.log("processEmbedPlayers::runPlayerSwap::" + $( playerElement ).attr('id') );

			var playerInterface = new mw.EmbedPlayer( playerElement );
			var inDomPlayer = swapEmbedPlayerElement( playerElement, playerInterface );

			// Trigger the newEmbedPlayerEvent for embedPlayer interface
			mw.log("processEmbedPlayers::tirgger:: newEmbedPlayerEvent " + inDomPlayer.id );

			// Allow plugins to add bindings to the inDomPlayer
			$( mw ).trigger ( 'newEmbedPlayerEvent', inDomPlayer );

			// Add a player ready binding:
			$( inDomPlayer ).bind( 'playerReady', areSelectedPlayersReady );

			// Issue the checkPlayerSources call to the new player
			// interface: make sure to use the element that is in the DOM:
			inDomPlayer.checkPlayerSources();
		}

		if( waitForMeta && mw.getConfig('EmbedPlayer.WaitForMeta' ) ) {
			mw.log('processEmbedPlayers::WaitForMeta ( video missing height (' +
					$( playerElement ).attr('height') + '), width (' +
					$( playerElement ).attr('width') + ') or duration: ' +
					$( playerElement ).attr('duration')
			);
			$( playerElement ).bind("loadedmetadata", runPlayerSwap );

			// Time-out of 5 seconds ( maybe still playable but no timely metadata )
			setTimeout( runPlayerSwap, 5000 );
			return ;
		} else {
			runPlayerSwap();
			return ;
		}
	};

	/**
	 * Check if we should wait for metadata.
	 *
	 * @return 	true if the size is "likely" to be updated by waiting for metadata
	 * 			false if the size has been set via an attribute or is already loaded
	 */
	var waitForMetaCheck = function( playerElement ){
		var waitForMeta = false;

		// Don't wait for metadata for non html5 media elements
		if( !playerElement ){
			return false;
		}
		if( !playerElement.tagName || ( playerElement.tagName.toLowerCase() != 'audio'  && playerElement.tagName.toLowerCase() != 'video' ) ){
			return false;
		}
		// If we don't have a native player don't wait for metadata
		if( !mw.EmbedTypes.getMediaPlayers().isSupportedPlayer( 'oggNative') &&
			!mw.EmbedTypes.getMediaPlayers().isSupportedPlayer( 'webmNative') &&
			!mw.EmbedTypes.getMediaPlayers().isSupportedPlayer( 'h264Native' ) )
		{
			return false;
		}

		var width = $( playerElement ).css( 'width' );
		var height = $( playerElement ).css( 'height' );

		// Css video defaults ( firefox )
		if( $( playerElement ).css( 'width' ) == '300px' &&
				$( playerElement ).css( 'height' ) == '150px'
		){
			waitForMeta = true;
		} else {
			// Check if we should wait for duration:
			if( $( playerElement ).attr( 'duration') ||
				$( playerElement ).attr('data-durationhint')
			){
				// height, width and duration set; do not wait for meta data:
				return false;
			} else {
				waitForMeta = true;
			}
		}

		// Firefox ~ sometimes~ gives -1 for unloaded media
		if ( $(playerElement).attr( 'width' ) == -1 || $(playerElement).attr( 'height' ) == -1 ) {
			waitForMeta = true;
		}

		// Google Chrome / safari gives 0 width height for unloaded media
		if( $(playerElement).attr( 'width' ) === 0 ||
			$(playerElement).attr( 'height' ) === 0
		) {
			waitForMeta = true;
		}

		// Firefox default width height is ~sometimes~ 150 / 300
		if( this.height == 150 && this.width == 300 ){
			waitForMeta = true;
		}

		// Make sure we have a src attribute or source child
		// ( i.e not a video tag to be dynamically populated or looked up from
		// xml resource description )
		if( waitForMeta &&
			(
				$( playerElement ).attr('src') ||
				$( playerElement ).find("source[src]").length !== 0
			)
		) {
			// Detect src type ( if no type set )
			return true;
		} else {
			// playerElement is not likely to update its meta data ( no src )
			return false;
		}
	};

	/**
	 * swapEmbedPlayerElement
	 *
	 * Takes a video element as input and swaps it out with an embed player interface
	 *
	 * @param {Element}
	 *      targetElement Element to be swapped
	 * @param {Object}
	 *      playerInterface Interface to swap into the target element
	 */
	var swapEmbedPlayerElement =  function( targetElement, playerInterface ) {
		mw.log( 'processEmbedPlayers::swapEmbedPlayerElement: ' + targetElement.id );
		// Create a new element to swap the player interface into
		var swapPlayerElement = document.createElement('div');

		// Add a class that identifies all embedPlayers:
		$( swapPlayerElement ).addClass( 'mwEmbedPlayer' );

		// Get properties / methods from playerInterface:
		for ( var method in playerInterface ) {
			if ( method != 'readyState' ) { // readyState crashes IE ( don't include )
				swapPlayerElement[ method ] = playerInterface[ method ];
			}
		}
		// Check if we are using native controls or Persistent player ( should keep the video embed around )
		if( playerInterface.useNativePlayerControls() || playerInterface.isPersistentNativePlayer() ) {
			$( targetElement )
			.attr( 'id', playerInterface.pid )
			.addClass( 'nativeEmbedPlayerPid' )
			.show()
			.after(
				$( swapPlayerElement ).css( 'display', 'none' )
			);

		} else {
			$( targetElement ).replaceWith( swapPlayerElement );
		}

		// Set swapPlayerElement has height / width set and set to loading:
		$( swapPlayerElement ).css( {
			'width' : playerInterface.width + 'px',
			'height' : playerInterface.height + 'px'
		} );

		// If we don't already have a loadSpiner add one:
		if( $('#loadingSpinner_' + playerInterface.id ).length == 0 && !$.browser.mozilla ){
			if( playerInterface.useNativePlayerControls() || playerInterface.isPersistentNativePlayer() ) {
				var $spinner = $( targetElement )
					.getAbsoluteOverlaySpinner();
			}else{
				var $spinner = $( swapPlayerElement ).getAbsoluteOverlaySpinner();
			}
			$spinner.attr('id', 'loadingSpinner_' + playerInterface.id );
		}
		return swapPlayerElement;
	};

	// Add a loader for <div> embed player rewrites:
	$( playerSelect ).each( function( index, playerElement) {

		// Make sure the playerElement has an id:
		if( !$( playerElement ).attr('id') ){
			$( playerElement ).attr( "id", 'mwe_v' + ( index ) );
		}
		// Add the player Id to the playerIdList
		playerIdList.push( $( playerElement ).attr( "id") );

		// If we are dynamically embedding on a "div" check if we can
		// add a poster image behind the loader:
		if( playerElement.nodeName.toLowerCase() == 'div'
			&& ( attributes.poster || $(playerElement).attr( 'poster' ) ) ){
			var posterSrc = ( attributes.poster ) ? attributes.poster : $(playerElement).attr( 'poster' );

			// Set image size:
			var width = $( playerElement ).width();
			var height = $( playerElement ).height();
			if( !width ){
				var width = ( attributes.width ) ? attributes.width : '100%';
			}
			if( !height ){
				var height = ( attributes.height ) ? attributes.height : '100%';
			}

			mw.log('EmbedPlayer:: set loading background: ' + posterSrc);
			$( playerElement ).append(
				$( '<img />' )
				.attr( 'src', posterSrc)
				.css({
					'position' : 'absolute',
					'width' : width,
					'height' : height
				})
			);
		}
	});

	// Deprecated EmbedPlayerManagerReady event ( should remove )
	$( mw ).trigger( 'EmbedPlayerManagerReady' );

	// Make sure we have user preference setup for setting preferences on video selection
	var addedPlayersFlag = false;
	mw.log("processEmbedPlayers:: Do: " + $( playerSelect ).length + ' players ');

	// Add each selected element to the player manager:
	$( playerSelect ).each( function( index, playerElement) {
		// Make sure the video tag was not generated by our library:
		if( $( playerElement ).hasClass( 'nativeEmbedPlayerPid' ) ){
			$( '#loadingSpinner_' + $( playerElement ).attr('id') ).remove();
			mw.log( 'processEmbedPlayers::$.embedPlayer skip embedPlayer gennerated video: ' + playerElement );
		} else {
			addedPlayersFlag = true;
			// Add the player
			addPlayerElement( playerElement );
		}
	});
	if( !addedPlayersFlag ){
		// Run the callback directly if no players were added
		if( callback ){
			callback();
		}
	}
};<commit_msg>Fixed jsCallback not fired.<commit_after>/**
 * Selector based embedPlayer processing
 *
 * @param {Function=}
 *      callback Optional Function to be called once video interfaces
 *      are ready
 *
 */
mw.processEmbedPlayers = function( playerSelect, callback ) {
	mw.log( 'processEmbedPlayers:: playerSelector: '+ playerSelect);

	// The player id list container
	var playerIdList = [];

	// Check if the selected player set is ready if ready issue the parent callback
	var areSelectedPlayersReady = function(){
		var playersLoaded = true;
		$.each(playerIdList, function(inx, playerId){
			if( ! $( '#' + playerId ).get(0).playerReady ){
				playersLoaded = false;
				return false;
			}
		})
		if( playersLoaded ){
			if( callback )
				callback();
		}
	}

	/**
	 * Adds a player element for the embedPlayer to rewrite
	 *
	 * uses embedPlayer interface on audio / video elements uses mvPlayList
	 * interface on playlist elements
	 *
	 * Once a player interface is established the following chain of functions
	 * are called;
	 *
	 * _this.checkPlayerSources()
	 * _this.setupSourcePlayer()
	 * _this.updatePlaybackInterface()
	 * _this.selectedPlayer.load()
	 * _this.showPlayer()
	 *
	 * @param {Element}
	 *      playerElement DOM element to be swapped
	 * @param {Object}
	 *      [Optional] attributes Extra attributes to apply to the player
	 *      interface
	 */
	var addPlayerElement = function( playerElement ) {
		var _this = this;
		mw.log('EmbedPlayer:: addElement:: ' + playerElement.id );

		// Be sure to "stop" the target ( Firefox 3x keeps playing
		// the video even though its been removed from the DOM )
		if( playerElement.pause ){
			playerElement.pause();
		}

		// Allow modules to override the wait for metadata flag:
		$( mw ).trigger( 'checkPlayerWaitForMetaData', playerElement );

		// DOM *could* load height, width and duration eventually, in some browsers
		// By default, don't bother waiting for this.
		var waitForMeta = false;

		// if a plugin has told us not to waitForMeta, don't
		if ( playerElement.waitForMeta !== false ) {
			// Check if we should wait for metadata, after all
			waitForMeta = waitForMetaCheck( playerElement );
		}

		var ranPlayerSwapFlag = false;

		// Local callback to runPlayer swap once playerElement has metadata
		function runPlayerSwap() {
			// Don't run player swap twice
			if( ranPlayerSwapFlag ){
				return ;
			}
			ranPlayerSwapFlag = true;
			mw.log("processEmbedPlayers::runPlayerSwap::" + $( playerElement ).attr('id') );

			var playerInterface = new mw.EmbedPlayer( playerElement );
			var inDomPlayer = swapEmbedPlayerElement( playerElement, playerInterface );

			// Trigger the newEmbedPlayerEvent for embedPlayer interface
			mw.log("processEmbedPlayers::tirgger:: newEmbedPlayerEvent " + inDomPlayer.id );

			// Allow plugins to add bindings to the inDomPlayer
			$( mw ).trigger ( 'newEmbedPlayerEvent', inDomPlayer );

			// Add a player ready binding:
			$( inDomPlayer ).bind( 'playerReady', areSelectedPlayersReady );

			//
			// Allow modules to block player build out
			//
			// this is needed in cases where you need to do an asynchronous
			// player interface setup. like iframes asynchronous announcing its ready for
			// bindings that can affect player setup.
			mw.log("EmbedPlayer::addPlayerElement :trigger startPlayerBuildOut:" + inDomPlayer.id );
			$( '#' + inDomPlayer.id ).triggerQueueCallback( 'startPlayerBuildOut', function(){
				// Issue the checkPlayerSources call to the new player
				// interface: make sure to use the element that is in the DOM:
				inDomPlayer.checkPlayerSources();
			});
		}

		if( waitForMeta && mw.getConfig('EmbedPlayer.WaitForMeta' ) ) {
			mw.log('processEmbedPlayers::WaitForMeta ( video missing height (' +
					$( playerElement ).attr('height') + '), width (' +
					$( playerElement ).attr('width') + ') or duration: ' +
					$( playerElement ).attr('duration')
			);
			$( playerElement ).bind("loadedmetadata", runPlayerSwap );

			// Time-out of 5 seconds ( maybe still playable but no timely metadata )
			setTimeout( runPlayerSwap, 5000 );
			return ;
		} else {
			runPlayerSwap();
			return ;
		}
	};

	/**
	 * Check if we should wait for metadata.
	 *
	 * @return 	true if the size is "likely" to be updated by waiting for metadata
	 * 			false if the size has been set via an attribute or is already loaded
	 */
	var waitForMetaCheck = function( playerElement ){
		var waitForMeta = false;

		// Don't wait for metadata for non html5 media elements
		if( !playerElement ){
			return false;
		}
		if( !playerElement.tagName || ( playerElement.tagName.toLowerCase() != 'audio'  && playerElement.tagName.toLowerCase() != 'video' ) ){
			return false;
		}
		// If we don't have a native player don't wait for metadata
		if( !mw.EmbedTypes.getMediaPlayers().isSupportedPlayer( 'oggNative') &&
			!mw.EmbedTypes.getMediaPlayers().isSupportedPlayer( 'webmNative') &&
			!mw.EmbedTypes.getMediaPlayers().isSupportedPlayer( 'h264Native' ) )
		{
			return false;
		}

		var width = $( playerElement ).css( 'width' );
		var height = $( playerElement ).css( 'height' );

		// Css video defaults ( firefox )
		if( $( playerElement ).css( 'width' ) == '300px' &&
				$( playerElement ).css( 'height' ) == '150px'
		){
			waitForMeta = true;
		} else {
			// Check if we should wait for duration:
			if( $( playerElement ).attr( 'duration') ||
				$( playerElement ).attr('data-durationhint')
			){
				// height, width and duration set; do not wait for meta data:
				return false;
			} else {
				waitForMeta = true;
			}
		}

		// Firefox ~ sometimes~ gives -1 for unloaded media
		if ( $(playerElement).attr( 'width' ) == -1 || $(playerElement).attr( 'height' ) == -1 ) {
			waitForMeta = true;
		}

		// Google Chrome / safari gives 0 width height for unloaded media
		if( $(playerElement).attr( 'width' ) === 0 ||
			$(playerElement).attr( 'height' ) === 0
		) {
			waitForMeta = true;
		}

		// Firefox default width height is ~sometimes~ 150 / 300
		if( this.height == 150 && this.width == 300 ){
			waitForMeta = true;
		}

		// Make sure we have a src attribute or source child
		// ( i.e not a video tag to be dynamically populated or looked up from
		// xml resource description )
		if( waitForMeta &&
			(
				$( playerElement ).attr('src') ||
				$( playerElement ).find("source[src]").length !== 0
			)
		) {
			// Detect src type ( if no type set )
			return true;
		} else {
			// playerElement is not likely to update its meta data ( no src )
			return false;
		}
	};

	/**
	 * swapEmbedPlayerElement
	 *
	 * Takes a video element as input and swaps it out with an embed player interface
	 *
	 * @param {Element}
	 *      targetElement Element to be swapped
	 * @param {Object}
	 *      playerInterface Interface to swap into the target element
	 */
	var swapEmbedPlayerElement =  function( targetElement, playerInterface ) {
		mw.log( 'processEmbedPlayers::swapEmbedPlayerElement: ' + targetElement.id );
		// Create a new element to swap the player interface into
		var swapPlayerElement = document.createElement('div');

		// Add a class that identifies all embedPlayers:
		$( swapPlayerElement ).addClass( 'mwEmbedPlayer' );

		// Get properties / methods from playerInterface:
		for ( var method in playerInterface ) {
			if ( method != 'readyState' ) { // readyState crashes IE ( don't include )
				swapPlayerElement[ method ] = playerInterface[ method ];
			}
		}
		// Check if we are using native controls or Persistent player ( should keep the video embed around )
		if( playerInterface.useNativePlayerControls() || playerInterface.isPersistentNativePlayer() ) {
			$( targetElement )
			.attr( 'id', playerInterface.pid )
			.addClass( 'nativeEmbedPlayerPid' )
			.show()
			.after(
				$( swapPlayerElement ).css( 'display', 'none' )
			);

		} else {
			$( targetElement ).replaceWith( swapPlayerElement );
		}

		// Set swapPlayerElement has height / width set and set to loading:
		$( swapPlayerElement ).css( {
			'width' : playerInterface.width + 'px',
			'height' : playerInterface.height + 'px'
		} );

		// If we don't already have a loadSpiner add one:
		if( $('#loadingSpinner_' + playerInterface.id ).length == 0 && !$.browser.mozilla ){
			if( playerInterface.useNativePlayerControls() || playerInterface.isPersistentNativePlayer() ) {
				var $spinner = $( targetElement )
					.getAbsoluteOverlaySpinner();
			}else{
				var $spinner = $( swapPlayerElement ).getAbsoluteOverlaySpinner();
			}
			$spinner.attr('id', 'loadingSpinner_' + playerInterface.id );
		}
		return swapPlayerElement;
	};

	// Add a loader for <div> embed player rewrites:
	$( playerSelect ).each( function( index, playerElement) {

		// Make sure the playerElement has an id:
		if( !$( playerElement ).attr('id') ){
			$( playerElement ).attr( "id", 'mwe_v' + ( index ) );
		}
		// Add the player Id to the playerIdList
		playerIdList.push( $( playerElement ).attr( "id") );

		// If we are dynamically embedding on a "div" check if we can
		// add a poster image behind the loader:
		if( playerElement.nodeName.toLowerCase() == 'div'
			&& ( attributes.poster || $(playerElement).attr( 'poster' ) ) ){
			var posterSrc = ( attributes.poster ) ? attributes.poster : $(playerElement).attr( 'poster' );

			// Set image size:
			var width = $( playerElement ).width();
			var height = $( playerElement ).height();
			if( !width ){
				var width = ( attributes.width ) ? attributes.width : '100%';
			}
			if( !height ){
				var height = ( attributes.height ) ? attributes.height : '100%';
			}

			mw.log('EmbedPlayer:: set loading background: ' + posterSrc);
			$( playerElement ).append(
				$( '<img />' )
				.attr( 'src', posterSrc)
				.css({
					'position' : 'absolute',
					'width' : width,
					'height' : height
				})
			);
		}
	});

	// Deprecated EmbedPlayerManagerReady event ( should remove )
	$( mw ).trigger( 'EmbedPlayerManagerReady' );

	// Make sure we have user preference setup for setting preferences on video selection
	var addedPlayersFlag = false;
	mw.log("processEmbedPlayers:: Do: " + $( playerSelect ).length + ' players ');

	// Add each selected element to the player manager:
	$( playerSelect ).each( function( index, playerElement) {
		// Make sure the video tag was not generated by our library:
		if( $( playerElement ).hasClass( 'nativeEmbedPlayerPid' ) ){
			$( '#loadingSpinner_' + $( playerElement ).attr('id') ).remove();
			mw.log( 'processEmbedPlayers::$.embedPlayer skip embedPlayer gennerated video: ' + playerElement );
		} else {
			addedPlayersFlag = true;
			// Add the player
			addPlayerElement( playerElement );
		}
	});
	if( !addedPlayersFlag ){
		// Run the callback directly if no players were added
		if( callback ){
			callback();
		}
	}
};<|endoftext|>


<commit_before>import React, {Component} from 'react';
import {format, isPast} from 'date-fns';

import {sortByDate} from './utils';
import styles from './App.scss';
import Footer from '../Footer';
import Link from '../Link';
import Heading from '../Heading';
import Icon from '../Icon';
import ConferenceList from '../ConferenceList';
import ConferenceFilter from '../ConferenceFilter';

const BASE_URL = 'https://raw.githubusercontent.com/nimzco/confs.tech/master/conferences';
const CURRENT_YEAR = (new Date()).getFullYear().toString();

export default class App extends Component {
  state = {
    filters: {
      year: '2017',
      type: 'javascript',
    },
    showPast: false,
    loading: true,
    conferences: [],
    sortDateDirection: 'asc',
  };

  componentDidMount() {
    this.loadConference();
  }

  loadConference = () => {
    const {filters} = this.state;
    this.setState({loading: true});

    fetch(getConferenceLink(filters))
      .then((result) => result.json())
      // eslint-disable-next-line promise/always-return
      .then((conferences) => {
        this.setState({
          loading: false,
          conferences: sortByDate(conferences, 'asc'),
        });
      })
      .catch((error) => {
        console.warn(error); // eslint-disable-line no-console
      });
  };

  handleYearChange = (year) => {
    const {filters} = this.state;

    this.setState(
      {
        filters: {...filters, year},
      },
      this.loadConference
    );
  };

  handleTypeChange = (type) => {
    const {filters} = this.state;

    this.setState(
      {
        filters: {...filters, type},
      },
      this.loadConference
    );
  };

  togglePast = () => {
    const {showPast} = this.state;
    this.setState({showPast: !showPast});
  };

  pastConferenceToggler = () => {
    const {showPast, filters: {year}} = this.state;

    if (CURRENT_YEAR !== year) {
      return null;
    }

    return (

        <Link onClick={this.togglePast}>
          {showPast ? 'Hide past conferences' : 'Show past conferences'}
        </Link>

    );
  };

  sortByDate = (direction) => {
    const {conferences} = this.state;

    this.setState({
      conferences: sortByDate(conferences, direction),
      sortDateDirection: direction,
    });
  };

  filterConferences = (conferences) => {
    const {showPast} = this.state;

    if (showPast) {
      return conferences;
    }

    return conferences.filter((conference) => {
      return !isPast(format(conference.startDate));
    });
  };

  render() {
    const {
      sortDateDirection,
      loading,
      conferences,
      filters: {year, type},
    } = this.state;

    return (

            <Heading element="h1">Find your next {TYPES[type]} conference</Heading>

            <ConferenceFilter
              sortDateDirection={sortDateDirection}
              sortByDate={this.sortByDate}
              year={year}
              type={type}
              onYearChange={this.handleYearChange}
              onTypeChange={this.handleTypeChange}
            />

            <Link
              url="https://github.com/nimzco/the-conference-list/issues/new"
              external
            >
              Add a conference
            </Link>
            {this.pastConferenceToggler()}

            {loading
              ? Loader()
              : <ConferenceList
                sortDateDirection={sortDateDirection}
                conferences={this.filterConferences(conferences)}
                />
            }

    );
  }
}

function getConferenceLink(state) {
  const {type, year} = state;
  return `${BASE_URL}/${year}/${type.toLocaleLowerCase()}.json`;
}

function Loader() {
  return (

      <Icon source="loading" size={64} />

  );
}
<commit_msg>Remove Add conference link<commit_after>import React, {Component} from 'react';
import {format, isPast} from 'date-fns';

import {sortByDate} from './utils';
import styles from './App.scss';
import Footer from '../Footer';
import Link from '../Link';
import Heading from '../Heading';
import Icon from '../Icon';
import ConferenceList from '../ConferenceList';
import ConferenceFilter from '../ConferenceFilter';

const BASE_URL = 'https://raw.githubusercontent.com/nimzco/confs.tech/master/conferences';
const CURRENT_YEAR = (new Date()).getFullYear().toString();

export default class App extends Component {
  state = {
    filters: {
      year: '2017',
      type: 'javascript',
    },
    showPast: false,
    loading: true,
    conferences: [],
    sortDateDirection: 'asc',
  };

  componentDidMount() {
    this.loadConference();
  }

  loadConference = () => {
    const {filters} = this.state;
    this.setState({loading: true});

    fetch(getConferenceLink(filters))
      .then((result) => result.json())
      // eslint-disable-next-line promise/always-return
      .then((conferences) => {
        this.setState({
          loading: false,
          conferences: sortByDate(conferences, 'asc'),
        });
      })
      .catch((error) => {
        console.warn(error); // eslint-disable-line no-console
      });
  };

  handleYearChange = (year) => {
    const {filters} = this.state;

    this.setState(
      {
        filters: {...filters, year},
      },
      this.loadConference
    );
  };

  handleTypeChange = (type) => {
    const {filters} = this.state;

    this.setState(
      {
        filters: {...filters, type},
      },
      this.loadConference
    );
  };

  togglePast = () => {
    const {showPast} = this.state;
    this.setState({showPast: !showPast});
  };

  pastConferenceToggler = () => {
    const {showPast, filters: {year}} = this.state;

    if (CURRENT_YEAR !== year) {
      return null;
    }

    return (

        <Link onClick={this.togglePast}>
          {showPast ? 'Hide past conferences' : 'Show past conferences'}
        </Link>

    );
  };

  sortByDate = (direction) => {
    const {conferences} = this.state;

    this.setState({
      conferences: sortByDate(conferences, direction),
      sortDateDirection: direction,
    });
  };

  filterConferences = (conferences) => {
    const {showPast} = this.state;

    if (showPast) {
      return conferences;
    }

    return conferences.filter((conference) => {
      return !isPast(format(conference.startDate));
    });
  };

  render() {
    const {
      sortDateDirection,
      loading,
      conferences,
      filters: {year, type},
    } = this.state;

    return (

            <Heading element="h1">Find your next {TYPES[type]} conference</Heading>

            <ConferenceFilter
              sortDateDirection={sortDateDirection}
              sortByDate={this.sortByDate}
              year={year}
              type={type}
              onYearChange={this.handleYearChange}
              onTypeChange={this.handleTypeChange}
            />
            {this.pastConferenceToggler()}

            {loading
              ? Loader()
              : <ConferenceList
                sortDateDirection={sortDateDirection}
                conferences={this.filterConferences(conferences)}
                />
            }

    );
  }
}

function getConferenceLink(state) {
  const {type, year} = state;
  return `${BASE_URL}/${year}/${type.toLocaleLowerCase()}.json`;
}

function Loader() {
  return (

      <Icon source="loading" size={64} />

  );
}
<|endoftext|>


<commit_before>import cuid from 'cuid';
import { storage, database } from '@/config/firebase';

const getFileExtension = str => str.substring(str.lastIndexOf('.'), str.length);

export default function Image(img = {}) {
  this.file = img.file || null;
  this.downloadURL = img.downloadURL || null;
  this.id = img.id || cuid();

  const imagesRef = database.ref(`/images/gallery/${this.id}`);

  // Methods
  this.url = () => {
    return this.file
      ? window.URL.createObjectURL(this.file)
      : this.downloadURL || null;
  };

  this.put = () => {
    if (!this.file) throw new Error('Can\'t upload image, no file specified');
    if (this.downloadURL) return Promise.resolve({ downloadURL: this.downloadURL });

    return storage
      .ref(`/gallery/${this.id}${getFileExtension(this.file.name)}`)
      .put(this.file, { customMetadata: { id: this.id } });
  };

  this.getThumbnail = (width) => {
    return new Promise((resolve) => {
      imagesRef.child(width).once('value', snapshot => resolve(snapshot.val()));
    });
  }
}
<commit_msg>Add thumbs prop<commit_after>import cuid from 'cuid';
import { storage, database } from '@/config/firebase';

const getFileExtension = str => str.substring(str.lastIndexOf('.'), str.length);

export default function Image(img = {}) {
  this.file = img.file || null;
  this.downloadURL = img.downloadURL || null;
  this.thumbnails = img.thumbnails || null;
  this.id = img.id || cuid();

  const imagesRef = database.ref(`/images/gallery/${this.id}`);

  // Methods
  this.url = () => {
    return this.file
      ? window.URL.createObjectURL(this.file)
      : this.downloadURL || null;
  };

  this.put = () => {
    if (!this.file) throw new Error('Can\'t upload image, no file specified');
    if (this.downloadURL) return Promise.resolve({ downloadURL: this.downloadURL });

    return storage
      .ref(`/gallery/${this.id}${getFileExtension(this.file.name)}`)
      .put(this.file, { customMetadata: { id: this.id } });
  };

  this.getThumbnail = (width) => {
    return new Promise((resolve) => {
      imagesRef.child(width).once('value', snapshot => resolve(snapshot.val()));
    });
  }
}
<|endoftext|>


<commit_before>var React = require('react');
var Stats = require("./Stats");
var BitcoinChart = require("./BitcoinChart");

var Bitcoin = React.createClass({
    handlePriceChange: function(priceObject){
        // Logic for handling the price change goes here
    },
    render: function() {
        return (

                <Stats onPriceChange={this.handlePriceChange} />
                <BitcoinChart />

        )
    }
});

module.exports = Bitcoin;

<commit_msg>Add the AlertForm to the Bitcoin component<commit_after>var React = require('react');
var Stats = require("./Stats");
var AlertForm = require("./AlertForm");
var BitcoinChart = require("./BitcoinChart");

var Bitcoin = React.createClass({
    handlePriceChange: function(priceObject){
        // Logic for handling the price change goes here
    },
    render: function() {
        return (

                <Stats onPriceChange={this.handlePriceChange} />
                <BitcoinChart />
                <AlertForm />

        )
    }
});

module.exports = Bitcoin;

<|endoftext|>


<commit_before>import React from 'react';
import _ from 'lodash';
import styled from 'styled-components';
import Paper from 'material-ui/Paper';
import { compose, withHandlers } from 'recompose';
import { connect } from 'react-redux';

import List, { ListItem, ListItemText, ListSubheader } from 'material-ui/List';

const Wrapper = styled(Paper).attrs({
  elevation: 0,
})`
  flex-grow: 1;
  display: flex;
`;

const DialLog = ({ entries, onListItemClick }) =>
  (<Wrapper>
    <List dense subheader={<ListSubheader>Your Call Log</ListSubheader>}>
      {_.map(entries, (entry, i) =>
        (<ListItem button key={i} data-phonenumber={entry.phoneNumber} onClick={onListItemClick}>
          <ListItemText primary={entry.phoneNumber} />
        </ListItem>),
      )}
    </List>
  </Wrapper>);

export default compose(
  connect((state) => state.callLog),
  withHandlers({
    onListItemClick: ({ dispatch }) => (e) => {
      dispatch({
        type: 'dialer/SET_PHONE_NUMBER',
        value: e.currentTarget.dataset['phonenumber'],
      });
    },
  }),
)(DialLog);
<commit_msg>Allow clicks on past calls only when call status is idle<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import styled from 'styled-components';
import Paper from 'material-ui/Paper';
import { compose, getContext, withHandlers, withPropsOnChange } from 'recompose';
import { CALL_STATUS_IDLE } from 'react-sip';
import { connect } from 'react-redux';

import List, { ListItem, ListItemText, ListSubheader } from 'material-ui/List';

const Wrapper = styled(Paper).attrs({
  elevation: 0,
})`
  flex-grow: 1;
  display: flex;
`;

const DialLog = ({ entries, onListItemClick, allowListItemClicks }) =>
  (<Wrapper>
    <List dense subheader={<ListSubheader>Your Call Log</ListSubheader>}>
      {_.map(entries, (entry, i) =>
        (<ListItem
          button={allowListItemClicks}
          key={i}
          data-phonenumber={entry.phoneNumber}
          onClick={onListItemClick}
        >
          <ListItemText primary={entry.phoneNumber} />
        </ListItem>),
      )}
    </List>
  </Wrapper>);

export default compose(
  getContext({
    callStatus: PropTypes.string,
  }),
  withPropsOnChange(['callStatus'], ({ callStatus }) => ({
    allowListItemClicks: callStatus === CALL_STATUS_IDLE,
  })),
  connect((state) => state.callLog),
  withHandlers({
    onListItemClick: ({ dispatch }) => (e) => {
      dispatch({
        type: 'dialer/SET_PHONE_NUMBER',
        value: e.currentTarget.dataset['phonenumber'],
      });
    },
  }),
)(DialLog);
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import withIntl from '../lib/withIntl';
import { defineMessages, FormattedMessage, FormattedDate } from 'react-intl';
import { graphql } from 'react-apollo'
import gql from 'graphql-tag'
import Avatar from './Avatar';
import Link from './Link';
import SmallButton from './SmallButton';
import { get, pick } from 'lodash';
import InputField from './InputField';

class Comment extends React.Component {

  static propTypes = {
    collective: PropTypes.object,
    comment: PropTypes.object,
    LoggedInUser: PropTypes.object
  }

  constructor(props) {
    super(props);

    this.state = {
      modified: false,
      comment: props.comment,
      mode: undefined
    };

    this.save = this.save.bind(this);
    this.handleChange = this.handleChange.bind(this);
    this.toggleEdit = this.toggleEdit.bind(this);
    this.messages = defineMessages({
      'edit': { id: 'comment.edit', defaultMessage: 'edit' },
      'cancelEdit': { id: 'comment.cancelEdit', defaultMessage: 'cancel edit' }
    });
    this.currencyStyle = { style: 'currency', currencyDisplay: 'symbol', minimumFractionDigits: 2, maximumFractionDigits: 2};
  }

  cancelEdit() {
    this.setState({ modified: false, mode: 'details' });
  }

  edit() {
    this.setState({ modified: false, mode: 'edit' });
  }

  toggleEdit() {
    this.state.mode === 'edit' ? this.cancelEdit() : this.edit();
  }

  handleChange(attr, value) {
    this.setState({
      modified: true,
      comment: {
        ...this.state.comment,
        [attr]: value
      }
    });
    window.state = { modified: true, comment: { ...this.state.comment, [attr]: value } };
  }

  async save() {
    const comment = pick(this.state.comment, ['id', 'markdown', 'html']);
    await this.props.editComment(comment);
    this.setState({ modified: false, mode: 'details' });
  }

  render() {
    const {
      intl,
      collective,
      LoggedInUser,
      editable
    } = this.props;

    const { comment } = this.state;
    if (!comment) return (
);
    const editor = (get(LoggedInUser, 'collective.settings.editor') === 'markdown' || get(collective, 'settings.editor') === 'markdown') ? 'markdown' : 'html';

    return (
      <div className={`comment ${this.state.mode}View`}>

          <a href={`/${comment.fromCollective.slug}`} title={comment.fromCollective.name}>
            <Avatar src={comment.fromCollective.image} key={comment.fromCollective.id} radius={40} />

              <FormattedDate value={comment.createdAt} day="numeric" month="numeric" /> |
              <Link route={`/${comment.fromCollective.slug}`}>{comment.fromCollective.name}</Link>
              { editable && LoggedInUser && LoggedInUser.canEditComment(comment) &&
                 | {intl.formatMessage(this.messages[`${this.state.mode === 'edit' ? 'cancelEdit' : 'edit'}`])}
              }

              { this.state.mode !== 'edit' &&
 }
              { this.state.mode === 'edit' && <InputField type={editor} defaultValue={comment[editor]} onChange={(value) => this.handleChange(editor, value)} />}

          { editable &&

              { this.state.mode === 'edit' &&

                  <SmallButton className="primary save" onClick={this.save} disabled={!this.state.modified}><FormattedMessage id="comment.save" defaultMessage="save" /></SmallButton>

              }

          }

    );
  }
}

const editCommentQuery = gql`
mutation editComment($comment: CommentAttributesInputType!) {
  editComment(comment: $comment) {
    id
    html
    createdAt
    collective {
      id
      slug
      currency
      name
      host {
        id
        slug
      }
      stats {
        id
        balance
      }
    }
    fromCollective {
      id
      type
      name
      slug
      image
    }
  }
}
`;

const addMutation = graphql(editCommentQuery, {
props: ( { mutate }) => ({
  editComment: async (comment) => {
    return await mutate({ variables: { comment } })
  }
})
});

export default withIntl(addMutation(Comment));
<commit_msg>fix truncating long comments<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import withIntl from '../lib/withIntl';
import { defineMessages, FormattedMessage, FormattedDate } from 'react-intl';
import { graphql } from 'react-apollo'
import gql from 'graphql-tag'
import Avatar from './Avatar';
import Link from './Link';
import SmallButton from './SmallButton';
import { get, pick } from 'lodash';
import InputField from './InputField';

class Comment extends React.Component {

  static propTypes = {
    collective: PropTypes.object,
    comment: PropTypes.object,
    LoggedInUser: PropTypes.object
  }

  constructor(props) {
    super(props);

    this.state = {
      modified: false,
      comment: props.comment,
      mode: undefined
    };

    this.save = this.save.bind(this);
    this.handleChange = this.handleChange.bind(this);
    this.toggleEdit = this.toggleEdit.bind(this);
    this.messages = defineMessages({
      'edit': { id: 'comment.edit', defaultMessage: 'edit' },
      'cancelEdit': { id: 'comment.cancelEdit', defaultMessage: 'cancel edit' }
    });
    this.currencyStyle = { style: 'currency', currencyDisplay: 'symbol', minimumFractionDigits: 2, maximumFractionDigits: 2};
  }

  cancelEdit() {
    this.setState({ modified: false, mode: 'details' });
  }

  edit() {
    this.setState({ modified: false, mode: 'edit' });
  }

  toggleEdit() {
    this.state.mode === 'edit' ? this.cancelEdit() : this.edit();
  }

  handleChange(attr, value) {
    this.setState({
      modified: true,
      comment: {
        ...this.state.comment,
        [attr]: value
      }
    });
    window.state = { modified: true, comment: { ...this.state.comment, [attr]: value } };
  }

  async save() {
    const comment = pick(this.state.comment, ['id', 'markdown', 'html']);
    await this.props.editComment(comment);
    this.setState({ modified: false, mode: 'details' });
  }

  render() {
    const {
      intl,
      collective,
      LoggedInUser,
      editable
    } = this.props;

    const { comment } = this.state;
    if (!comment) return (
);
    const editor = (get(LoggedInUser, 'collective.settings.editor') === 'markdown' || get(collective, 'settings.editor') === 'markdown') ? 'markdown' : 'html';

    return (
      <div className={`comment ${this.state.mode}View`}>

          <a href={`/${comment.fromCollective.slug}`} title={comment.fromCollective.name}>
            <Avatar src={comment.fromCollective.image} key={comment.fromCollective.id} radius={40} />

              <FormattedDate value={comment.createdAt} day="numeric" month="numeric" /> |
              <Link route={`/${comment.fromCollective.slug}`}>{comment.fromCollective.name}</Link>
              { editable && LoggedInUser && LoggedInUser.canEditComment(comment) &&
                 | {intl.formatMessage(this.messages[`${this.state.mode === 'edit' ? 'cancelEdit' : 'edit'}`])}
              }

              { this.state.mode !== 'edit' &&
 }
              { this.state.mode === 'edit' && <InputField type={editor} defaultValue={comment[editor]} onChange={(value) => this.handleChange(editor, value)} />}

          { editable &&

              { this.state.mode === 'edit' &&

                  <SmallButton className="primary save" onClick={this.save} disabled={!this.state.modified}><FormattedMessage id="comment.save" defaultMessage="save" /></SmallButton>

              }

          }

    );
  }
}

const editCommentQuery = gql`
mutation editComment($comment: CommentAttributesInputType!) {
  editComment(comment: $comment) {
    id
    html
    createdAt
    collective {
      id
      slug
      currency
      name
      host {
        id
        slug
      }
      stats {
        id
        balance
      }
    }
    fromCollective {
      id
      type
      name
      slug
      image
    }
  }
}
`;

const addMutation = graphql(editCommentQuery, {
props: ( { mutate }) => ({
  editComment: async (comment) => {
    return await mutate({ variables: { comment } })
  }
})
});

export default withIntl(addMutation(Comment));
<|endoftext|>


<commit_before>import firebase from 'config/firebase';
import cuid from 'cuid';

const defaultSkill = {
  name: '',
};

export default class Skill {
  constructor(skill) {
    this.id = cuid();

    const populated = Object.assign({}, defaultSkill, skill);
    for (const key in populated) {
      if (populated.hasOwnProperty(key)) {
        this[key] = populated[key];
      }
    }
  }

  set() {
    return firebase
      .database()
      .ref(`/skills/${this.id}`)
      .set(this);
  }

  remove() {
    return firebase
      .database()
      .ref(`/skills/${this.id}`)
      .remove();
  }
}
<commit_msg>Update skill model<commit_after>import firebase from 'config/firebase';
import cuid from 'cuid';

export default class Skill {
  constructor(skill) {
    this.id = cuid();
    this.name = '';
    this.user = null;
    this.created = Date.now();

    // Prefill values from payload
    const populated = Object.assign({}, this, skill);
    for (const key in populated) {
      if (populated.hasOwnProperty(key)) {
        this[key] = populated[key];
      }
    }
  }

  set() {
    return firebase
      .database()
      .ref(`/skills/${this.id}`)
      .set(this);
  }

  remove() {
    return firebase
      .database()
      .ref(`/skills/${this.id}`)
      .remove();
  }
}
<|endoftext|>


<commit_before>var array = [3, 4, 5, 6, 1, 2];

/*find the peak when given an sorted array and number of rotations
 *@param a Array
 *@param n integer
 */
var findPeak = function (a, n) {
    //    debugger;
    if (a.length === 1 || (a.length !== 0 || n == 0) {
        return a[a.length - 1];
    } else if (a.length > 1) {
        var peakLocLeftShift = a[a.length - ((n % a.length) + 1)];
        var index = (n % a.length) - 1;
        if (index === undefined || index < 0) index = 0;
        var peakLocRightShift = a[index];
        if (peakLocLeftShift > peakLocRightShift) {
            return peakLocLeftShift;
        } else {
            return peakLocRightShift;
        }
    } else {
        throw "Array is empty";
    }
};
//test cases
console.log(findPeak(array, 2));
console.log(findPeak(array, 4));
console.log(findPeak([1], 4));
console.log(findPeak([1], 0));
console.log(findPeak([1, 2], 0));
console.log(findPeak([1, 1], 2));
console.log(findPeak([], 2));
/*Test Output
6
6
1
1
2
1
"Array is empty"
*/
<commit_msg>rearrange the array<commit_after>/*Question : Given an array and is rotated n number of times find the number where the peak happens.
*The array is sorted in increasing order.
*Follow up question: How will you rearrange them in normal sorted order.
*/

var array = [3, 4, 5, 6, 1, 2];

/*find the peak when given an sorted array and number of rotations
 *@param a Array
 *@param n integer
 */
var findPeak = function (a, n) {
    //    debugger;
    if (a.length === 1 || (a.length !== 0 || n == 0) {
        return a[a.length - 1];
    } else if (a.length > 1) {
        var peakLocLeftShift = a[a.length - ((n % a.length) + 1)];
        var index = (n % a.length) - 1;
        if (index === undefined || index < 0) index = 0;
        var peakLocRightShift = a[index];
        if (peakLocLeftShift > peakLocRightShift) {
            return peakLocLeftShift;
        } else {
            return peakLocRightShift;
        }
    } else {
        throw "Array is empty";
    }
};

//rearranges the array
var rearrangeArray = function (a, n) {
    try {
        if (a.length < 2) {
            return a;
        }
        debugger;
        var highest = findPeak(a, n);
        var oIndex = a.indexOf(highest);
        var part1 = a.slice(oIndex + 1, a.length);
        var part2 = a.slice(0, oIndex + 1);
        var result = part1;
        for (var i = 0; i < part2.length; i++) {
            result.push(part2[i]);
        }
        return result;
    } catch (e) {
        console.log(e.message);
    }
};
//test cases
console.log(rearrangeArray(array, 2));
console.log(findPeak(array, 2));
console.log(findPeak(array, 4));
console.log(findPeak([1], 4));
console.log(findPeak([1], 0));
console.log(findPeak([1, 2], 0));
console.log(findPeak([1, 1], 2));
console.log(findPeak([], 2));
/*Test Output
[1,2,3,4,5,6]
6
6
1
1
2
1
"Array is empty"
*/
<|endoftext|>