<commit_before>/**
 * @overview ccm component for building a comment component
 * @author Tea Kless <tea.kless@web.de>, 2017
 * @license The MIT License (MIT)
 * @version latest (1.0.0)
 */

( function () {

  var component = {

  name: 'comment_builder',
  version: [ 1,0,0 ],

  ccm: {
      url: 'https://akless.github.io/ccm/version/ccm-10.0.0.min.js',
      integrity: 'sha384-bCcBv9yCHVcXtsHxkfPcFeT+j77G112ZADZ1DkxcYdxjflPG4lTiiFiB3Jp+c2NG',
      crossorigin: 'anonymous'
    },

  config: {
    templates: {
      "main": {
        "class": "container",
        "inner": [
          {
            "tag": "legend",
            "class": "text-primary",
            "inner": "Build your Comment Component "
          },
          {
            "tag": "form",
            "class": "form-horizontal",
            "onsubmit": "%submit%",
            "inner": [
              {
                "class": "sign-on form-group",
                "inner": [
                  {
                    "tag": "label",
                    "class": "control-label col-md-2",
                    "inner": "Sign-on:"
                  },
                  {
                    "class": "col-md-10",
                    "inner": {
                      "tag": "select",
                      "onchange": "%user%",
                      "class": "user form-control",
                      "name": "user",
                      "inner": [
                        {
                          "tag":"option",
                          "inner": "None",
                          "value": ""
                        },
                        {
                          "tag":"option",
                          "inner": "Guest Mode",
                          "value": "['ccm.instance','https://akless.github.io/ccm-components/user/ccm.user.js',{'sign_on':'guest'}]"
                        },
                        {
                          "tag":"option",
                          "inner": "Demo Mode",
                          "value": "['ccm.instance','https://akless.github.io/ccm-components/user/ccm.user.js',{'sign_on':'demo'}]"
                        },
                        {
                          "tag":"option",
                          "inner": "H-BRS FB02",
                          "value": "['ccm.instance','https://akless.github.io/ccm-components/user/ccm.user.js',{'sign_on':'hbrsinfkaul'}]"
                        }
                      ]
                    }
                  }
                ]
              },
              {
                "class": "comment-template form-group",
                "inner": [
                  {
                    "tag": "label",
                    "class": "control-label col-md-2",
                    "inner": "Used Template:"
                  },
                  {
                    "class": "col-md-10",
                    "inner": {
                      "tag": "select",
                      "onchange": "%comment_template%",
                      "class": "user form-control",
                      "name": "comment_template",
                      "inner": [
                        {
                          "tag":"option",
                          "inner": "Simple",
                          "value": "simple"
                        },
                        {
                          "tag":"option",
                          "inner": "Expand",
                          "value": "expand"
                        }
                      ]
                    }
                  }
                ]
              },
              {
                "class": "voting form-group",
                "inner": [
                  {
                    "tag": "label",
                    "class": "control-label col-md-2",
                    "inner": "Used Voting Component:"
                  },
                  {
                    "class": "col-md-10",
                    "inner": {
                      "tag": "select",
                      "onchange": "%change_voting%",
                      "class": "user form-control",
                      "name": "voting",
                      "inner": [
                        {
                          "tag":"option",
                          "inner": "None",
                          "value": ""
                        },
                        {
                          "tag":"option",
                          "inner": "Voting",
                          "value": "[ 'ccm.component', 'https://tkless.github.io/ccm-components/voting/versions/ccm.voting-1.0.0.js', {'icon_likes': 'fa fa-lg fa-chevron-up', 'icon_dislikes': 'fa fa-lg fa-chevron-down', 'data': {'store': [ 'ccm.store', 'https://tkless.github.io/ccm-components/voting/voting_datastore.js' ]}} ]"
                        },
                        {
                          "tag":"option",
                          "inner": "Thumb up/down",
                          "value": "[ 'ccm.component', 'https://tkless.github.io/ccm-components/thumb_rating/versions/ccm.thumb_rating-1.0.0.js', { 'key': [ 'ccm.get', 'https://tkless.github.io/ccm-components/thumb_rating/thumb_rating_configs.js', 'demo' ] } ]"
                        }
                      ]
                    }
                  }
                ]
              },
              {
                "class": "sorting_by_voting form-group",
                "inner": [
                  {
                    "tag": "label",
                    "class": "control-label col-md-2",
                    "inner": "Sorting comments by voting:"
                  },
                  {
                    "class": "col-md-10",
                    "inner": {
                      "class": "checkbox",
                      "onchange": "%change_sorting%",
                      "inner": {
                        "tag": "label",
                        "inner": {
                          "tag": "input",
                          "type": "checkbox",
                          "name": "sorting_by_voting"
                        }
                      }
                    }
                  }

                ]
              },
              {
                "class": "editable form-group",
                "inner": [
                  {
                    "tag": "label",
                    "class": "control-label col-md-2",
                    "inner": "Editable:"
                  },
                  {
                    "class": "col-md-10",
                    "inner": {
                      "class": "checkbox",
                      "onchange": "%change_editable%",
                      "inner": {
                        "tag": "label",
                        "inner": {
                          "tag": "input",
                          "type": "checkbox",
                          "name": "editable"
                        }
                      }
                    }
                  }

                ]
              },
              {
                "inner": [
                  {
                    "tag": "legend",
                    "class": "legend text-primary",
                    "inner": "As it already looks like..."
                  },
                  {
                    "id": "preview"
                  }
                ]
              },
              {
                "class": "submit-button form-group",
                "inner": [

                  {
                    "class": "col-md-12 text-right",
                    "inner": {
                      "tag": "button",
                      "type": "submit",
                      "class": "btn btn-primary",
                      "inner": "Save App"
                    }
                  }
                ]

              }
            ]
          }
        ]
      }
    },

    bootstrap_css: [ 'ccm.load', 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css', { url: 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css', context:'head' } ],
    preview: [ 'ccm.component', 'https://tkless.github.io/ccm-components/comment/versions/ccm.comment-1.0.0.js' ]
  },

  Instance: function () {
    var self = this;

    this.start = function (callback) {
      self.ccm.helper.setContent( self.element, self.ccm.helper.html( self.templates.main, {
        submit: renderPreview,
        user: renderPreview,
        comment_template: renderPreview,
        change_voting: renderPreview,
        change_sorting: renderPreview,
        change_editable: renderPreview
      } ));

      function renderPreview() {
        self.element.querySelector( '#preview' ).innerHTML = '

';
        var config_data = prepareResultData();

        config_data.data = {
          store: [ 'ccm.store', 'https://tkless.github.io/ccm-components/comment/comment_datastore.js' ],
            key: 'demo'
        };

        config_data.root = self.element.querySelector( '#preview div' );
        self.preview.start( config_data );

        function prepareResultData() {
          var config_data = self.ccm.helper.formData( self.element.querySelector( 'form' ) );

          self.ccm.helper.decodeDependencies( config_data );
          return config_data;
        }

      }

      if ( callback ) callback();
    };
  }
  };

  function p(){window.ccm[v].component(component)}var f="ccm."+component.name+(component.version?"-"+component.version.join("."):"")+".js";if(window.ccm&&null===window.ccm.files[f])window.ccm.files[f]=component;else{var n=window.ccm&&window.ccm.components[component.name];n&&n.ccm&&(component.ccm=n.ccm),"string"==typeof component.ccm&&(component.ccm={url:component.ccm});var v=component.ccm.url.split("/").pop().split("-");if(v.length>1?(v=v[1].split("."),v.pop(),"min"===v[v.length-1]&&v.pop(),v=v.join(".")):v="latest",window.ccm&&window.ccm[v])p();else{var e=document.createElement("script");document.head.appendChild(e),component.ccm.integrity&&e.setAttribute("integrity",component.ccm.integrity),component.ccm.crossorigin&&e.setAttribute("crossorigin",component.ccm.crossorigin),e.onload=function(){p(),document.head.removeChild(e)},e.src=component.ccm.url}}
}() );<commit_msg>update v.1 of comment-builder<commit_after>/**
 * @overview ccm component for building a comment component
 * @author Tea Kless <tea.kless@web.de>, 2017
 * @license The MIT License (MIT)
 * @version latest (1.0.0)
 */

( function () {

  var component = {

  name: 'comment_builder',
  version: [ 1,0,0 ],

  ccm: {
      url: 'https://akless.github.io/ccm/version/ccm-10.0.0.min.js',
      integrity: 'sha384-bCcBv9yCHVcXtsHxkfPcFeT+j77G112ZADZ1DkxcYdxjflPG4lTiiFiB3Jp+c2NG',
      crossorigin: 'anonymous'
    },

  config: {
    templates: {
      "main": {
        "inner": [
          {
            "tag": "legend",
            "class": "text-primary",
            "inner": "Build your Comment Component "
          },
          {
            "tag": "form",
            "class": "form-horizontal",
            "onsubmit": "%submit%",
            "inner": [
              {
                "class": "sign-on form-group",
                "inner": [
                  {
                    "tag": "label",
                    "class": "control-label col-md-2",
                    "inner": "Sign-on:"
                  },
                  {
                    "class": "col-md-10",
                    "inner": {
                      "tag": "select",
                      "onchange": "%user%",
                      "class": "user form-control",
                      "name": "user",
                      "inner": [
                        {
                          "tag":"option",
                          "inner": "None",
                          "value": ""
                        },
                        {
                          "tag":"option",
                          "inner": "Guest Mode",
                          "value": "['ccm.instance','https://akless.github.io/ccm-components/user/ccm.user.js',{'sign_on':'guest'}]"
                        },
                        {
                          "tag":"option",
                          "inner": "Demo Mode",
                          "value": "['ccm.instance','https://akless.github.io/ccm-components/user/ccm.user.js',{'sign_on':'demo'}]"
                        },
                        {
                          "tag":"option",
                          "inner": "H-BRS FB02",
                          "value": "['ccm.instance','https://akless.github.io/ccm-components/user/ccm.user.js',{'sign_on':'hbrsinfkaul'}]"
                        }
                      ]
                    }
                  }
                ]
              },
              {
                "class": "comment-template form-group",
                "inner": [
                  {
                    "tag": "label",
                    "class": "control-label col-md-2",
                    "inner": "Used Template:"
                  },
                  {
                    "class": "col-md-10",
                    "inner": {
                      "tag": "select",
                      "onchange": "%comment_template%",
                      "class": "user form-control",
                      "name": "comment_template",
                      "inner": [
                        {
                          "tag":"option",
                          "inner": "Simple",
                          "value": "simple"
                        },
                        {
                          "tag":"option",
                          "inner": "Expand",
                          "value": "expand"
                        }
                      ]
                    }
                  }
                ]
              },
              {
                "class": "voting form-group",
                "inner": [
                  {
                    "tag": "label",
                    "class": "control-label col-md-2",
                    "inner": "Used Voting Component:"
                  },
                  {
                    "class": "col-md-10",
                    "inner": {
                      "tag": "select",
                      "onchange": "%change_voting%",
                      "class": "user form-control",
                      "name": "voting",
                      "inner": [
                        {
                          "tag":"option",
                          "inner": "None",
                          "value": ""
                        },
                        {
                          "tag":"option",
                          "inner": "Voting",
                          "value": "[ 'ccm.component', 'https://tkless.github.io/ccm-components/voting/versions/ccm.voting-1.0.0.js', {'icon_likes': 'fa fa-lg fa-chevron-up', 'icon_dislikes': 'fa fa-lg fa-chevron-down', 'data': {'store': [ 'ccm.store', 'https://tkless.github.io/ccm-components/voting/voting_datastore.js' ]}} ]"
                        },
                        {
                          "tag":"option",
                          "inner": "Thumb up/down",
                          "value": "[ 'ccm.component', 'https://tkless.github.io/ccm-components/thumb_rating/versions/ccm.thumb_rating-1.0.0.js', { 'key': [ 'ccm.get', 'https://tkless.github.io/ccm-components/thumb_rating/thumb_rating_configs.js', 'demo' ] } ]"
                        }
                      ]
                    }
                  }
                ]
              },
              {
                "class": "sorting_by_voting form-group",
                "inner": [
                  {
                    "tag": "label",
                    "class": "control-label col-md-2",
                    "inner": "Sorting comments by voting:"
                  },
                  {
                    "class": "col-md-10",
                    "inner": {
                      "class": "checkbox",
                      "onchange": "%change_sorting%",
                      "inner": {
                        "tag": "label",
                        "inner": {
                          "tag": "input",
                          "type": "checkbox",
                          "name": "sorting_by_voting"
                        }
                      }
                    }
                  }

                ]
              },
              {
                "class": "editable form-group",
                "inner": [
                  {
                    "tag": "label",
                    "class": "control-label col-md-2",
                    "inner": "Editable:"
                  },
                  {
                    "class": "col-md-10",
                    "inner": {
                      "class": "checkbox",
                      "onchange": "%change_editable%",
                      "inner": {
                        "tag": "label",
                        "inner": {
                          "tag": "input",
                          "type": "checkbox",
                          "name": "editable"
                        }
                      }
                    }
                  }

                ]
              },
              {
                "inner": [
                  {
                    "tag": "legend",
                    "class": "legend text-primary",
                    "inner": "As it already looks like..."
                  },
                  {
                    "id": "preview"
                  }
                ]
              },
              {
                "class": "submit-button form-group",
                "inner": [

                  {
                    "class": "col-md-12 text-right",
                    "inner": {
                      "tag": "button",
                      "type": "submit",
                      "class": "btn btn-primary",
                      "inner": "Save App"
                    }
                  }
                ]

              }
            ]
          }
        ]
      }
    },

    bootstrap_css: [ 'ccm.load', 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css', { url: 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css', context:'head' } ],
    preview: [ 'ccm.component', 'https://tkless.github.io/ccm-components/comment/versions/ccm.comment-1.0.0.js' ]
  },

  Instance: function () {
    var self = this;

    this.start = function (callback) {
      self.ccm.helper.setContent( self.element, self.ccm.helper.html( self.templates.main, {
        submit: renderPreview,
        user: renderPreview,
        comment_template: renderPreview,
        change_voting: renderPreview,
        change_sorting: renderPreview,
        change_editable: renderPreview
      } ));

      function renderPreview() {
        self.element.querySelector( '#preview' ).innerHTML = '

';
        var config_data = prepareResultData();

        config_data.data = {
          store: [ 'ccm.store', 'https://tkless.github.io/ccm-components/comment/comment_datastore.js' ],
            key: 'demo'
        };

        config_data.root = self.element.querySelector( '#preview div' );
        self.preview.start( config_data );

        function prepareResultData() {
          var config_data = self.ccm.helper.formData( self.element.querySelector( 'form' ) );

          self.ccm.helper.decodeDependencies( config_data );
          return config_data;
        }

      }

      if ( callback ) callback();
    };
  }
  };

  function p(){window.ccm[v].component(component)}var f="ccm."+component.name+(component.version?"-"+component.version.join("."):"")+".js";if(window.ccm&&null===window.ccm.files[f])window.ccm.files[f]=component;else{var n=window.ccm&&window.ccm.components[component.name];n&&n.ccm&&(component.ccm=n.ccm),"string"==typeof component.ccm&&(component.ccm={url:component.ccm});var v=component.ccm.url.split("/").pop().split("-");if(v.length>1?(v=v[1].split("."),v.pop(),"min"===v[v.length-1]&&v.pop(),v=v.join(".")):v="latest",window.ccm&&window.ccm[v])p();else{var e=document.createElement("script");document.head.appendChild(e),component.ccm.integrity&&e.setAttribute("integrity",component.ccm.integrity),component.ccm.crossorigin&&e.setAttribute("crossorigin",component.ccm.crossorigin),e.onload=function(){p(),document.head.removeChild(e)},e.src=component.ccm.url}}
}() );<|endoftext|>


<commit_before>/**
 * Module dependencies.
 */

var block = require('./comments-block');
var item = require('./comment-item');
var serialize = require('serialize');
var request = require('request');
var toArray = require('to-array');
var Emitter = require('emitter');
var domify = require('domify');
var events = require('events');
var truncate = require('truncate');
var tip = require('tip')
var o = require('query');
var Paragraphs = require('paragraphs');
var classes = require('classes');
var closest = require('closest');
var citizen = require('citizen');
var t = require('t');
var log = require('debug')('democracyos:comments-view');

/**
 * Expose comments view
 */

module.exports = CommentsView;

/**
 * View constructor
 *
 * @param {String} context
 * @param {String} reference
 * @constructor
 */

function CommentsView(context, reference) {
  if (!(this instanceof CommentsView)) {
    return new CommentsView(context, reference);
  };

  this.context = context;
  this.reference = reference;

  this.el = domify(block({
    citizen: citizen,
    context: context,
    reference: reference,
    t: t,
    truncate: truncate
  }));

  this.comments = [];

  this.events = events(this.el, this);
  this.switchOn();
}

/**
 * Mixin Emitter
 */

Emitter(CommentsView.prototype);

/**
 * Description
 */

CommentsView.prototype.render = function(el) {
  if (1 === arguments.length) {

    // if string, then query element
    if ('string' === typeof el) {
      el = o(el);
    };

    // if it's not currently inserted
    // at `el`, then append to `el`
    if (el !== this.el.parentNode) {
      el.appendChild(this.el);
    };

    // !!!: Should we return different things
    // on different conditions?
    // Or should we be consistent with
    // render returning always `this.el`
    return this;
  };

  return this.el;
}

/**
 * Switch on events
 *
 * @api public
 */

CommentsView.prototype.switchOn = function() {
  this.events.bind('submit form.comment-form');
  this.events.bind('click .toggle-deleted-comments', 'toggleDeletedComments');
  this.events.bind('click a.like', 'onlike');
  this.events.bind('click a.dislike', 'ondislike');
  this.events.bind('click a.flag', 'onflag');
  this.events.bind('click span.show-spam a', 'onshowspam');
  this.events.bind('click .comment-text a.read-more', 'readmore');
  this.on('fetch', this.load.bind(this));
  this.on('post', this.add.bind(this));
}

/**
 * Read full comment
 *
 * @api public
 */

CommentsView.prototype.readmore = function(ev) {
  ev.preventDefault();

  var commentContainer = closest(ev.target,'li[data-id]');
  var id = commentContainer.getAttribute('data-id');
  var comment = get(this.comments, 'id === "%id"'.replace('%id', id));
  var commentText = o('.comment-text', commentContainer);

  commentText.innerHTML = new Paragraphs(comment.text).split();
}

/**
 * Switch off events
 *
 * @api public
 */

CommentsView.prototype.switchOff = function() {
  this.off('fetch');
  this.off('post');
  this.events.unbind();
}

/**
 * Submit event handler
 *
 * @param {Event} ev
 * @api private
 */

CommentsView.prototype.onsubmit = function(ev) {
  ev.preventDefault();

  var data = serialize.object(ev.target);
  var errors = this.validate(data);
  this.errors(errors)
  if (errors.length) return log('Found errors: %o', errors);
  this.emit('submit', data);
  this.post(data);
}

/**
 * Validate form's fields
 *
 * @param {Object} data
 * @return {Array} of Errors
 * @api public
 */
CommentsView.prototype.validate = function(data) {
  var errors = [];
  if (!data.text) {
    errors.push('Comment cannot be empty');
  };
  if (data.text.length > 4096) {
    errors.push('Comment is limited to 4096 characters');
  };
  return errors;
}

/**
 * Fill errors list
 *
 * @param {Array} errors
 * @api public
 */

CommentsView.prototype.errors = function(errors) {
  var span = o('span.help-text.form-errors', this.el);
  errors = errors || [];
  classes(o('form', this.el)).remove('has-error');

  span.innerText = '';

  span.innerText = '';
  errors.forEach(function(err) {
    span.innerText += t(err);
  });

  classes(o('form', this.el)).add('has-error');
}

/**
 * Action like comment
 */

CommentsView.prototype.onlike = function(ev) {
  ev.preventDefault();

  var target = ev.delegateTarget || closest(ev.target, 'a');
  var comment = closest(target,'li[data-id]');
  var id = comment.getAttribute('data-id');
  var liked = classes(o('a.like', comment)).has('selected');
  var disliked = classes(o('a.dislike', comment)).has('selected');

  classes(target).add('selected');
  classes(o('a.dislike', comment)).remove('selected');

  var counter = o('.comment-counter', comment);
  var count = parseInt(counter.innerHTML, 10) || 0;
  count += disliked ? 2 : (liked ? 0 : 1);
  counter.innerHTML = count;

  request
  .post('/api/comment/:id/upvote'.replace(':id', id))
  .end(function(err, res) {
    if (err) return log('Fetch error: %s', err), classes(target).remove('selected');
    if (!res.ok) return log('Fetch error: %s', res.error), classes(target).remove('selected');
    if (res.body && res.body.error) return log('Fetch response error: %s', res.body.error), classes(target).remove('selected');
    log('successfull upvote %s', id);
  });
}

/**
 * Action dislike comment
 */

CommentsView.prototype.ondislike = function(ev) {
  ev.preventDefault();

  var target = ev.delegateTarget || closest(ev.target, 'a');
  var comment = closest(target,'li[data-id]');
  var id = comment.getAttribute('data-id');
  var liked = classes(o('a.like', comment)).has('selected');
  var disliked = classes(o('a.dislike', comment)).has('selected');

  classes(target).add('selected');
  classes(o('a.like', comment)).remove('selected');

  var counter = o('.comment-counter', comment);
  var count = parseInt(counter.innerHTML, 10) || 0;
  count -= liked ? 2 : (disliked ? 0 : 1);
  counter.innerHTML = count;

  request
  .post('/api/comment/:id/downvote'.replace(':id', id))
  .end(function(err, res) {
    if (err) return log('Fetch error: %s', err), classes(target).remove('selected');
    if (!res.ok) return log('Fetch error: %s', res.error), classes(target).remove('selected');
    if (res.body.error) return log('Fetch response error: %s', res.body.error), classes(target).remove('selected');
    log('successfull downvote %s', id);
  });
}

/**
 * Action flag comment
 */

CommentsView.prototype.onflag = function(ev) {
  ev.preventDefault();

  var target = ev.delegateTarget || closest(ev.target, 'a');
  var comment = closest(target,'li[data-id]');
  var id = comment.getAttribute('data-id');
  var flagged = classes(target).has('selected');

  classes(target).toggle('selected');
  flagged ? target.title=t('Spam') : target.title=t('Not spam');
  flagged ? classes(comment).remove('spam') : classes(comment).add('spam');

  request
  .post('/api/comment/:id/:action'.replace(':id', id).replace(':action', flagged ? 'unflag' : 'flag'))
  .end(function(err, res) {
    if (err) return log('Fetch error: %s', err), classes(target).remove('selected');
    if (!res.ok) return log('Fetch error: %s', res.error), classes(target).remove('selected');
    if (res.body.error) return log('Fetch response error: %s', res.body.error), classes(target).remove('selected');

    tip(target);
    log('successfull %s as spam %s', flagged ? 'unflag' : 'flag', id);
  });
}

/**
 * Show spam comment
 */

CommentsView.prototype.onshowspam = function(ev) {
  ev.preventDefault();

  var target = ev.delegateTarget || closest(ev.target, 'a');
  var comment = closest(target,'li[data-id]');
  var id = comment.getAttribute('data-id');
  var flagged = classes(target).has('selected');
  flagged ? target.title=t('Spam') : target.title=t('Not spam');

  classes(comment).remove('spam');
}

/**
 * Fetch comments
 *
 * @api public
 */

CommentsView.prototype.fetch = function() {
  var view = this;
  request
  .get(this.url() + '/comments')
  .end(function(err, res) {
    if (err) {
      log('Fetch error: %s', err);
      return;
    };
    if (!res.ok) {
      log('Fetch error: %s', res.error);
      return;
    };
    if (res.body.error) {
      log('Fetch response error: %s', res.body.error);
      return;
    };
    view.emit('fetch', res.body);
  });
}

/**
 * Post a comment
 *
 * @param {Object} data
 * @api public
 */

CommentsView.prototype.post = function(data) {
  var view = this;
  data.context = this.context;
  data.reference = this.reference;

  request
  .post(this.url() + "/comment")
  .send({ comment: data })
  .end(function(err, res) {

    if (res.body && res.body.error) {
      return log('Fetch response error: %s', res.body.error), view.errors([res.body.error]);
    };

    if (err || !res.ok) return log('Fetch error: %s', err || res.error);

    view.emit('post', res.body);
    view.comments.push(res.body);
    view.clear();
  });
}

/**
 * Load comments in view's `el`
 *
 * @param {Array} comments
 * @api public
 */

CommentsView.prototype.load = function(comments) {
  if (!comments.length) return;

  log('load %o', comments);

  this.comments.push.apply(this.comments, comments);

  this.comments
  .reverse()
  .sort(function(a, b) {
    var first = a.upvotes.length - a.downvotes.length;
    var second = b.upvotes.length - b.downvotes.length;
    var sort = (first - second);
    return 0 !== sort ? sort / Math.abs(sort) : sort;
  })
  .forEach(function(comment) {
    this.add(comment);
  }, this);

  this.emit('load');
}

/**
 * Add comment to block list
 */

CommentsView.prototype.add = function(comment) {
  var blockEl = o('ul.media-list.comment-list.main-list', this.el);
  var deletedBlockEl = o('ul.media-list.comment-list.deleted-list', this.el);
  var commentEl = domify(item({ comment: comment, citizen: citizen, t: t, truncate: truncate, paragraphs: new Paragraphs(null, 230, '... ', 2) }));

  if (!comment.deleted) {
    var flag = o('.flag', commentEl);
    tip(flag);
    if (blockEl.children.length) {
      blockEl.insertBefore(commentEl, blockEl.children[0]);
    } else {
      blockEl.appendChild(commentEl);
    }
  } else {
    classes(o('.toggle-deleted-comments', this.el)).remove('hide');
    if (deletedBlockEl.children.length) {
      deletedBlockEl.insertBefore(commentEl, blockEl.children[0]);
    } else {
      deletedBlockEl.appendChild(commentEl);
    }
  }
}

/**
 * Clear form's inputs and textareas.
 *
 * @api public
 */

CommentsView.prototype.clear = function() {
  var els = o.all('input[type="text"], textarea', this.el);

  toArray(els).forEach(function(el) {
    switch ((el.tagName || el.nodeName).toLowerCase()) {
      case 'input':
      case 'textarea':
        el.value = '';
        break;
      case 'select':
        toArray(el.children)
          .forEach(function(option, i) {
            if (!i) return option.setAttribute('selected', 'selected');
            option.removeAttribute('selected');
          });
        break;
    }
  });
}

/**
 * Toggle deleted comments box
 *
 * @param {Event} ev
 * @api private
 */

CommentsView.prototype.toggleDeletedComments = function(ev) {
  ev.preventDefault();

  classes(o('ul.comment-list.deleted-list', this.el)).toggle('hide');
};

/**
 * Get api route
 */

CommentsView.prototype.url = function() {
  return "/api/{context}/{reference}"
    .replace('{context}', this.context)
    .replace('{reference}', this.reference);
}

function get(list, query) {
  var match;
  var test = new Function('_', 'return _.' + query);

  list.some(function(l) {
    if (test(l)) {
      match = l;
      return true;
    };
    return false;
  });
  return match || null;
}<commit_msg>[comments-view] - Fix random bug on responses without body<commit_after>/**
 * Module dependencies.
 */

var block = require('./comments-block');
var item = require('./comment-item');
var serialize = require('serialize');
var request = require('request');
var toArray = require('to-array');
var Emitter = require('emitter');
var domify = require('domify');
var events = require('events');
var truncate = require('truncate');
var tip = require('tip')
var o = require('query');
var Paragraphs = require('paragraphs');
var classes = require('classes');
var closest = require('closest');
var citizen = require('citizen');
var t = require('t');
var log = require('debug')('democracyos:comments-view');

/**
 * Expose comments view
 */

module.exports = CommentsView;

/**
 * View constructor
 *
 * @param {String} context
 * @param {String} reference
 * @constructor
 */

function CommentsView(context, reference) {
  if (!(this instanceof CommentsView)) {
    return new CommentsView(context, reference);
  };

  this.context = context;
  this.reference = reference;

  this.el = domify(block({
    citizen: citizen,
    context: context,
    reference: reference,
    t: t,
    truncate: truncate
  }));

  this.comments = [];

  this.events = events(this.el, this);
  this.switchOn();
}

/**
 * Mixin Emitter
 */

Emitter(CommentsView.prototype);

/**
 * Description
 */

CommentsView.prototype.render = function(el) {
  if (1 === arguments.length) {

    // if string, then query element
    if ('string' === typeof el) {
      el = o(el);
    };

    // if it's not currently inserted
    // at `el`, then append to `el`
    if (el !== this.el.parentNode) {
      el.appendChild(this.el);
    };

    // !!!: Should we return different things
    // on different conditions?
    // Or should we be consistent with
    // render returning always `this.el`
    return this;
  };

  return this.el;
}

/**
 * Switch on events
 *
 * @api public
 */

CommentsView.prototype.switchOn = function() {
  this.events.bind('submit form.comment-form');
  this.events.bind('click .toggle-deleted-comments', 'toggleDeletedComments');
  this.events.bind('click a.like', 'onlike');
  this.events.bind('click a.dislike', 'ondislike');
  this.events.bind('click a.flag', 'onflag');
  this.events.bind('click span.show-spam a', 'onshowspam');
  this.events.bind('click .comment-text a.read-more', 'readmore');
  this.on('fetch', this.load.bind(this));
  this.on('post', this.add.bind(this));
}

/**
 * Read full comment
 *
 * @api public
 */

CommentsView.prototype.readmore = function(ev) {
  ev.preventDefault();

  var commentContainer = closest(ev.target,'li[data-id]');
  var id = commentContainer.getAttribute('data-id');
  var comment = get(this.comments, 'id === "%id"'.replace('%id', id));
  var commentText = o('.comment-text', commentContainer);

  commentText.innerHTML = new Paragraphs(comment.text).split();
}

/**
 * Switch off events
 *
 * @api public
 */

CommentsView.prototype.switchOff = function() {
  this.off('fetch');
  this.off('post');
  this.events.unbind();
}

/**
 * Submit event handler
 *
 * @param {Event} ev
 * @api private
 */

CommentsView.prototype.onsubmit = function(ev) {
  ev.preventDefault();

  var data = serialize.object(ev.target);
  var errors = this.validate(data);
  this.errors(errors)
  if (errors.length) return log('Found errors: %o', errors);
  this.emit('submit', data);
  this.post(data);
}

/**
 * Validate form's fields
 *
 * @param {Object} data
 * @return {Array} of Errors
 * @api public
 */
CommentsView.prototype.validate = function(data) {
  var errors = [];
  if (!data.text) {
    errors.push('Comment cannot be empty');
  };
  if (data.text.length > 4096) {
    errors.push('Comment is limited to 4096 characters');
  };
  return errors;
}

/**
 * Fill errors list
 *
 * @param {Array} errors
 * @api public
 */

CommentsView.prototype.errors = function(errors) {
  var span = o('span.help-text.form-errors', this.el);
  errors = errors || [];
  classes(o('form', this.el)).remove('has-error');

  span.innerText = '';

  span.innerText = '';
  errors.forEach(function(err) {
    span.innerText += t(err);
  });

  classes(o('form', this.el)).add('has-error');
}

/**
 * Action like comment
 */

CommentsView.prototype.onlike = function(ev) {
  ev.preventDefault();

  var target = ev.delegateTarget || closest(ev.target, 'a');
  var comment = closest(target,'li[data-id]');
  var id = comment.getAttribute('data-id');
  var liked = classes(o('a.like', comment)).has('selected');
  var disliked = classes(o('a.dislike', comment)).has('selected');

  classes(target).add('selected');
  classes(o('a.dislike', comment)).remove('selected');

  var counter = o('.comment-counter', comment);
  var count = parseInt(counter.innerHTML, 10) || 0;
  count += disliked ? 2 : (liked ? 0 : 1);
  counter.innerHTML = count;

  request
  .post('/api/comment/:id/upvote'.replace(':id', id))
  .end(function(err, res) {
    if (err) return log('Fetch error: %s', err), classes(target).remove('selected');
    if (!res.ok) return log('Fetch error: %s', res.error), classes(target).remove('selected');
    if (res.body && res.body.error) return log('Fetch response error: %s', res.body.error), classes(target).remove('selected');
    log('successfull upvote %s', id);
  });
}

/**
 * Action dislike comment
 */

CommentsView.prototype.ondislike = function(ev) {
  ev.preventDefault();

  var target = ev.delegateTarget || closest(ev.target, 'a');
  var comment = closest(target,'li[data-id]');
  var id = comment.getAttribute('data-id');
  var liked = classes(o('a.like', comment)).has('selected');
  var disliked = classes(o('a.dislike', comment)).has('selected');

  classes(target).add('selected');
  classes(o('a.like', comment)).remove('selected');

  var counter = o('.comment-counter', comment);
  var count = parseInt(counter.innerHTML, 10) || 0;
  count -= liked ? 2 : (disliked ? 0 : 1);
  counter.innerHTML = count;

  request
  .post('/api/comment/:id/downvote'.replace(':id', id))
  .end(function(err, res) {
    if (err) return log('Fetch error: %s', err), classes(target).remove('selected');
    if (!res.ok) return log('Fetch error: %s', res.error), classes(target).remove('selected');
    if (res.body && res.body.error) return log('Fetch response error: %s', res.body.error), classes(target).remove('selected');
    log('successfull downvote %s', id);
  });
}

/**
 * Action flag comment
 */

CommentsView.prototype.onflag = function(ev) {
  ev.preventDefault();

  var target = ev.delegateTarget || closest(ev.target, 'a');
  var comment = closest(target,'li[data-id]');
  var id = comment.getAttribute('data-id');
  var flagged = classes(target).has('selected');

  classes(target).toggle('selected');
  flagged ? target.title=t('Spam') : target.title=t('Not spam');
  flagged ? classes(comment).remove('spam') : classes(comment).add('spam');

  request
  .post('/api/comment/:id/:action'.replace(':id', id).replace(':action', flagged ? 'unflag' : 'flag'))
  .end(function(err, res) {
    if (err) return log('Fetch error: %s', err), classes(target).remove('selected');
    if (!res.ok) return log('Fetch error: %s', res.error), classes(target).remove('selected');
    if (res.body && res.body.error) return log('Fetch response error: %s', res.body.error), classes(target).remove('selected');

    tip(target);
    log('successfull %s as spam %s', flagged ? 'unflag' : 'flag', id);
  });
}

/**
 * Show spam comment
 */

CommentsView.prototype.onshowspam = function(ev) {
  ev.preventDefault();

  var target = ev.delegateTarget || closest(ev.target, 'a');
  var comment = closest(target,'li[data-id]');
  var id = comment.getAttribute('data-id');
  var flagged = classes(target).has('selected');
  flagged ? target.title=t('Spam') : target.title=t('Not spam');

  classes(comment).remove('spam');
}

/**
 * Fetch comments
 *
 * @api public
 */

CommentsView.prototype.fetch = function() {
  var view = this;
  request
  .get(this.url() + '/comments')
  .end(function(err, res) {
    if (err) {
      log('Fetch error: %s', err);
      return;
    };
    if (!res.ok) {
      log('Fetch error: %s', res.error);
      return;
    };
    if (res.body && res.body.error) {
      log('Fetch response error: %s', res.body.error);
      return;
    };
    view.emit('fetch', res.body);
  });
}

/**
 * Post a comment
 *
 * @param {Object} data
 * @api public
 */

CommentsView.prototype.post = function(data) {
  var view = this;
  data.context = this.context;
  data.reference = this.reference;

  request
  .post(this.url() + "/comment")
  .send({ comment: data })
  .end(function(err, res) {

    if (res.body && res.body.error) {
      return log('Fetch response error: %s', res.body.error), view.errors([res.body.error]);
    };

    if (err || !res.ok) return log('Fetch error: %s', err || res.error);

    view.emit('post', res.body);
    view.comments.push(res.body);
    view.clear();
  });
}

/**
 * Load comments in view's `el`
 *
 * @param {Array} comments
 * @api public
 */

CommentsView.prototype.load = function(comments) {
  if (!comments.length) return;

  log('load %o', comments);

  this.comments.push.apply(this.comments, comments);

  this.comments
  .reverse()
  .sort(function(a, b) {
    var first = a.upvotes.length - a.downvotes.length;
    var second = b.upvotes.length - b.downvotes.length;
    var sort = (first - second);
    return 0 !== sort ? sort / Math.abs(sort) : sort;
  })
  .forEach(function(comment) {
    this.add(comment);
  }, this);

  this.emit('load');
}

/**
 * Add comment to block list
 */

CommentsView.prototype.add = function(comment) {
  var blockEl = o('ul.media-list.comment-list.main-list', this.el);
  var deletedBlockEl = o('ul.media-list.comment-list.deleted-list', this.el);
  var commentEl = domify(item({ comment: comment, citizen: citizen, t: t, truncate: truncate, paragraphs: new Paragraphs(null, 230, '... ', 2) }));

  if (!comment.deleted) {
    var flag = o('.flag', commentEl);
    tip(flag);
    if (blockEl.children.length) {
      blockEl.insertBefore(commentEl, blockEl.children[0]);
    } else {
      blockEl.appendChild(commentEl);
    }
  } else {
    classes(o('.toggle-deleted-comments', this.el)).remove('hide');
    if (deletedBlockEl.children.length) {
      deletedBlockEl.insertBefore(commentEl, blockEl.children[0]);
    } else {
      deletedBlockEl.appendChild(commentEl);
    }
  }
}

/**
 * Clear form's inputs and textareas.
 *
 * @api public
 */

CommentsView.prototype.clear = function() {
  var els = o.all('input[type="text"], textarea', this.el);

  toArray(els).forEach(function(el) {
    switch ((el.tagName || el.nodeName).toLowerCase()) {
      case 'input':
      case 'textarea':
        el.value = '';
        break;
      case 'select':
        toArray(el.children)
          .forEach(function(option, i) {
            if (!i) return option.setAttribute('selected', 'selected');
            option.removeAttribute('selected');
          });
        break;
    }
  });
}

/**
 * Toggle deleted comments box
 *
 * @param {Event} ev
 * @api private
 */

CommentsView.prototype.toggleDeletedComments = function(ev) {
  ev.preventDefault();

  classes(o('ul.comment-list.deleted-list', this.el)).toggle('hide');
};

/**
 * Get api route
 */

CommentsView.prototype.url = function() {
  return "/api/{context}/{reference}"
    .replace('{context}', this.context)
    .replace('{reference}', this.reference);
}

function get(list, query) {
  var match;
  var test = new Function('_', 'return _.' + query);

  list.some(function(l) {
    if (test(l)) {
      match = l;
      return true;
    };
    return false;
  });
  return match || null;
}<|endoftext|>


<commit_before>/*
 *  Copyright 2014 TWO SIGMA OPEN SOURCE, LLC
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
/**
 * JavaScript eval plugin
 * For creating and config evaluators that evaluate JavaScript code and update code cell results.
 */
define(function(require, exports, module) {
  'use strict';
  var PLUGIN_NAME = "JavaScript";
  var stringProps = ("charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight " +
      "toUpperCase toLowerCase split concat match replace search").split(" ");
  var arrayProps = ("length concat join splice push pop shift unshift slice reverse sort indexOf " +
      "lastIndexOf every some filter forEach map reduce reduceRight ").split(" ");
  var funcProps = "prototype apply call bind".split(" ");
  var javascriptKeywords = ("break case catch continue debugger default delete do else false finally for function " +
      "if in instanceof new null return switch throw true try typeof var void while with").split(" ");
  var coffeescriptKeywords = ("and break catch class continue delete do else extends false finally for " +
      "if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" ");

  var LANGUAGE_VERSIONS = {
    "ES5": {name: "ECMAScript 5"},
    "ES2015": {name: "ECMAScript 2015", babel: true, defaultVersion: true}
  };

  // Not using the es2015 plugin preset because it includes the "transform-es2015-modules-commonjs" plugin which puts forces strict mode on
  var ES2015Plugins = [
  "check-es2015-constants",
  "transform-es2015-arrow-functions",
  "transform-es2015-block-scoped-functions",
  "transform-es2015-block-scoping",
  "transform-es2015-classes",
  "transform-es2015-computed-properties",
  "transform-es2015-destructuring",
  "transform-es2015-for-of",
  "transform-es2015-function-name",
  "transform-es2015-literals",
  "transform-es2015-object-super",
  "transform-es2015-parameters",
  "transform-es2015-shorthand-properties",
  "transform-es2015-spread",
  "transform-es2015-sticky-regex",
  "transform-es2015-template-literals",
  "transform-es2015-typeof-symbol",
  "transform-es2015-unicode-regex",
  "transform-regenerator"
  ];

  var getCompletions = function(token, context, keywords, options) {
    var found = [], start = token.string;

    function maybeAdd(str) {
      if (str.indexOf(start) === 0 && !_.contains(found, str))
        found.push(str);
    }

    function gatherCompletions(obj) {
      if (typeof obj === "string")
        _.each(stringProps, maybeAdd);
      else if (obj instanceof Array)
        _.each(arrayProps, maybeAdd);
      else if (obj instanceof Function)
        _.each(funcProps, maybeAdd);
      for (var name in obj)
        maybeAdd(name);
    }

    if (context) {
      // If this is a property, see if it belongs to some object we can
      // find in the current environment.
      var obj = context.pop(), base;
      if (obj.type.indexOf("variable") === 0) {
        if (options && options.additionalContext)
          base = options.additionalContext[obj.string];
        base = base || window[obj.string];
      } else if (obj.type === "string") {
        base = "";
      } else if (obj.type === "atom") {
        base = 1;
      } else if (obj.type === "function") {
        if (window.jQuery !== null && (obj.string === '$' || obj.string === 'jQuery') &&
            (typeof window.jQuery === 'function'))
          base = window.jQuery();
        else if (window._ !== null && (obj.string === '_') && (typeof window._ === 'function'))
          base = window._();
      }
      while (base !== null && context.length)
        base = base[context.pop().string];
      if (base !== null)
        gatherCompletions(base);
    }
    else {
      // If not, just look in the window object and any local scope
      // (reading into JS mode internals to get at the local and global variables)
      for (var v = token.state.localVars; v; v = v.next)
        maybeAdd(v.name);
      for (var v = token.state.globalVars; v; v = v.next)
        maybeAdd(v.name);
      gatherCompletions(window);
      _.each(keywords, maybeAdd);
    }
    return found;
  };
  var Pos = CodeMirror.Pos;

  function scriptHint(editor, keywords, getToken, options) {
    // Find the token at the cursor
    var cur = editor.getCursor(), token = getToken(editor, cur), tprop = token;
    token.state = CodeMirror.innerMode(editor.getMode(), token.state).state;

    // If it's not a 'word-style' token, ignore the token.
    if (!/^[\w$_]*$/.test(token.string)) {
      token = tprop = {start: cur.ch, end: cur.ch, string: "", state: token.state,
        type: token.string === "." ? "property" : null};
    }
    // If it is a property, find out what it is a property of.
    while (tprop.type === "property") {
      tprop = getToken(editor, Pos(cur.line, tprop.start));
      if (tprop.string !== ".")
        return;
      tprop = getToken(editor, Pos(cur.line, tprop.start));
      if (tprop.string === ')') {
        var level = 1;
        do {
          tprop = getToken(editor, Pos(cur.line, tprop.start));
          switch (tprop.string) {
            case ')':
              level++;
              break;
            case '(':
              level--;
              break;
            default:
              break;
          }
        } while (level > 0);
        tprop = getToken(editor, Pos(cur.line, tprop.start));
        if (tprop.type.indexOf("variable") === 0)
          tprop.type = "function";
        else
          return; // no clue
      }
      if (!context)
        var context = [];
      context.push(tprop);
    }
    return getCompletions(token, context, keywords, options);
  }

  var JavascriptCancelFunction = null;

  function showErrorState(err) {
    bkHelper.receiveEvaluationUpdate(err.modelOutput,
        {status: "ERROR", payload: err.payload},
        PLUGIN_NAME);
  }

  var JavaScript_0 = {
    pluginName: PLUGIN_NAME,
    cmMode: "javascript",
    background: "#FFE0F0",
    bgColor: "#EFDB52",
    fgColor: "#4A4A4A",
    borderColor: "",
    shortName: "Js",
    tooltip: "The native scripting language of the web",
    evaluate: function(code, modelOutput, refreshObj) {
      var deferred = bkHelper.newDeferred();
      var self = this;
      bkHelper.timeout(function () {
        // this is visible to JS code in cell
        var beakerObj = bkHelper.getBeakerObject();
        try {
          var progressObj = {
            type: "BeakerDisplay",
            innertype: "Progress",
            object: {
              message: "running...",
              startTime: new Date().getTime(),
              outputdata: []
            }
          };
          modelOutput.result = progressObj;
          if (refreshObj !== undefined)
            refreshObj.outputRefreshed();
          else
            bkHelper.refreshRootScope();

          beakerObj.setupBeakerObject(modelOutput);
          beakerObj.notebookToBeakerObject();
          window.beaker = beakerObj.beakerObj;
          try {
            if (LANGUAGE_VERSIONS[self.settings.languageVersion].babel) {
              code = Babel.transform(code, { plugins: ES2015Plugins }).code;
            } else {
              acorn.parse(code);
            }
          } catch (e) {
            showErrorState({
              modelOutput: modelOutput,
              payload: e.message
            });
            beakerObj.clearOutput();
            return deferred.reject();
          }
          // (0, eval) is an indirect eval call to evaluate in non-strict mode
          // more info: http://stackoverflow.com/questions/19357978/indirect-eval-call-in-strict-mode
          var output = (0, eval)(code);
          beakerObj.beakerObjectToNotebook();
          if ( typeof output === 'object' ) {
            if(typeof output.promise === 'object' && typeof output.promise.then === 'function') {
              output = output.promise;
            }
            if(typeof output.then === 'function') {
              JavascriptCancelFunction = function () {
                showErrorState({
                  modelOutput: modelOutput,
                  payload: 'cancelled...'
                });
                if (modelOutput !== undefined && modelOutput.object !== undefined)
                  modelOutput.elapsedTime = new Date().getTime() - modelOutput.object.startTime;
                JavascriptCancelFunction = null;
                beakerObj.beakerObjectToNotebook();
                if ( typeof output.reject === 'function') {
                  output.reject();
                  output = undefined;
                } else {
                  deferred.reject();
                }
              };
              output.then(function(o) {
                JavascriptCancelFunction = null;
                if (beakerObj.isCircularObject(o))
                  o = "ERROR: circular objects are not supported";
                else
                  o = beakerObj.transform(o);
                bkHelper.receiveEvaluationUpdate(modelOutput, {status: "FINISHED", payload: o}, PLUGIN_NAME);
                beakerObj.beakerObjectToNotebook();
                deferred.resolve(o);
                beakerObj.clearOutput();
              }, function(e) {
                JavascriptCancelFunction = null;
                bkHelper.receiveEvaluationUpdate(modelOutput,
                    {status: "ERROR", payload: e}, PLUGIN_NAME);
                console.log(e);
                beakerObj.beakerObjectToNotebook();
                var r;
                if (beakerObj.isCircularObject(modelOutput.result))
                  r = "ERROR: circular objects are not supported";
                else
                  r = beakerObj.transform(modelOutput.result);
                deferred.reject(r);
                beakerObj.clearOutput();
              });
            } else {
              if (!beakerObj.isBeakerObject(output) && beakerObj.isCircularObject(output))
                output = "ERROR: circular objects are not supported";
              else
                output = beakerObj.transform(output);
              bkHelper.receiveEvaluationUpdate(modelOutput, {status: "FINISHED", payload: output}, PLUGIN_NAME);
              deferred.resolve(output);
              beakerObj.clearOutput();
            }
          } else {
            bkHelper.receiveEvaluationUpdate(modelOutput, {status: "FINISHED", payload: output}, PLUGIN_NAME);
            deferred.resolve(output);
            beakerObj.clearOutput();
          }
        } catch (err) {
          // detect chrome or firefox stack traces
          var stack = [];
          try {
            if (err.fileName !== undefined) {
              var stackin = err.stack.split(/\n/);
              stack.push(err.name+': '+err.message);
              var stage = 0;
              for(var i=0; i<stackin.length; i++) {
                var line = stackin[i];
                if (line.indexOf(' > eval:')>=0) {
                  var a = line.indexOf('@');
                  var b = line.indexOf(' > eval:');
                  stage = 1;
                  stack.push('    at '+line.substring(0,a)+' <codecell>'+line.substring(b+7));
                } else if(stage==0) {
                  stack.push(line);
                } else
                  break;
              }
              if (stack.length>1)
                stack.pop();
            } else {
              var stackin = err.stack.split(/\n/);

              stack.push(stackin[0]);
              for(var i=1; i<stackin.length; i++) {
                var line = stackin[i];
                if (line.indexOf(' at eval (eval at <anonymous> ')>=0)
                    break;
                    if (line.indexOf(' (eval at <anonymous>')>=0 && line.indexOf(', <anonymous>:')>=0 ) {
                      var a = line.indexOf(' (eval at <anonymous>');
                          var b = line.indexOf(', <anonymous>:')+13;
                          var c = line.indexOf(')',b);
                          stack.push(line.substring(0,a)+' <codecell>'+line.substring(b,c));
                          } else
                          stack.push(line);
                          }
                          }
                          } catch(e) { }
                          showErrorState({
                            modelOutput: modelOutput,
                            payload: stack
                          });
                          console.log(stack);
                          beakerObj.clearOutput();
                          var r;
                          if (beakerObj.isCircularObject(modelOutput.result))
                          r = "ERROR: circular objects are not supported";
                          else
                          r = beakerObj.transform(modelOutput.result);
                          deferred.reject(r);
        }
      }, 0);
      return deferred.promise;
    },
    interrupt: function() {
      this.cancelExecution();
    },
    cancelExecution: function () {
      if (JavascriptCancelFunction) {
        JavascriptCancelFunction();
      }
    },

    autocomplete2: function(editor, options, cb) {
      var ret = scriptHint(editor, javascriptKeywords,
          function(e, cur) {
            return e.getTokenAt(cur);
          },
          options);
      cb(ret,undefined,true);
    },
    exit: function(cb) {
      this.cancelExecution();
      JavascriptCancelFunction = null;
    },
    spec: {
      languageVersion: {
        type: "settableEnum",
        name: "JavaScript Version",
        values: _.mapValues(LANGUAGE_VERSIONS, function(version) {return version.name;})}
    }
  };
  var JavaScript0 = function(settings) {
    if (!settings.view) {
      settings.view = {};
    }
    if (!settings.view.cm) {
      settings.view.cm = {};
    }
    settings.view.cm.mode = JavaScript_0.cmMode;
    settings.view.cm.background = JavaScript_0.background;
    var defaultSettings = {
      languageVersion: _.findKey(LANGUAGE_VERSIONS, function (version) {
        return version.defaultVersion;
      })
    };
    this.settings = _.merge({}, defaultSettings, settings);
    this.perform = function(what) {
      var action = this.spec[what].action;
      this[action]();
    };
  };
  JavaScript0.prototype = JavaScript_0;

  exports.getEvaluatorFactory = function() {
    return bkHelper.getEvaluatorFactory(bkHelper.newPromise(JavaScript0));
  };
  exports.name = PLUGIN_NAME;
});
<commit_msg>#3676 - Defaulting to default JS version if the version info is wrong in bkr file<commit_after>/*
 *  Copyright 2014 TWO SIGMA OPEN SOURCE, LLC
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
/**
 * JavaScript eval plugin
 * For creating and config evaluators that evaluate JavaScript code and update code cell results.
 */
define(function(require, exports, module) {
  'use strict';
  var PLUGIN_NAME = "JavaScript";
  var stringProps = ("charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight " +
      "toUpperCase toLowerCase split concat match replace search").split(" ");
  var arrayProps = ("length concat join splice push pop shift unshift slice reverse sort indexOf " +
      "lastIndexOf every some filter forEach map reduce reduceRight ").split(" ");
  var funcProps = "prototype apply call bind".split(" ");
  var javascriptKeywords = ("break case catch continue debugger default delete do else false finally for function " +
      "if in instanceof new null return switch throw true try typeof var void while with").split(" ");
  var coffeescriptKeywords = ("and break catch class continue delete do else extends false finally for " +
      "if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" ");

  var LANGUAGE_VERSIONS = {
    "ES5": {name: "ECMAScript 5"},
    "ES2015": {name: "ECMAScript 2015", babel: true, defaultVersion: true}
  };

  function getDefaultLanguageVersion() {
    return _.findKey(LANGUAGE_VERSIONS, function (version) {
      return version.defaultVersion;
    });
  }

  // Not using the es2015 plugin preset because it includes the "transform-es2015-modules-commonjs" plugin which puts forces strict mode on
  var ES2015Plugins = [
  "check-es2015-constants",
  "transform-es2015-arrow-functions",
  "transform-es2015-block-scoped-functions",
  "transform-es2015-block-scoping",
  "transform-es2015-classes",
  "transform-es2015-computed-properties",
  "transform-es2015-destructuring",
  "transform-es2015-for-of",
  "transform-es2015-function-name",
  "transform-es2015-literals",
  "transform-es2015-object-super",
  "transform-es2015-parameters",
  "transform-es2015-shorthand-properties",
  "transform-es2015-spread",
  "transform-es2015-sticky-regex",
  "transform-es2015-template-literals",
  "transform-es2015-typeof-symbol",
  "transform-es2015-unicode-regex",
  "transform-regenerator"
  ];

  var getCompletions = function(token, context, keywords, options) {
    var found = [], start = token.string;

    function maybeAdd(str) {
      if (str.indexOf(start) === 0 && !_.contains(found, str))
        found.push(str);
    }

    function gatherCompletions(obj) {
      if (typeof obj === "string")
        _.each(stringProps, maybeAdd);
      else if (obj instanceof Array)
        _.each(arrayProps, maybeAdd);
      else if (obj instanceof Function)
        _.each(funcProps, maybeAdd);
      for (var name in obj)
        maybeAdd(name);
    }

    if (context) {
      // If this is a property, see if it belongs to some object we can
      // find in the current environment.
      var obj = context.pop(), base;
      if (obj.type.indexOf("variable") === 0) {
        if (options && options.additionalContext)
          base = options.additionalContext[obj.string];
        base = base || window[obj.string];
      } else if (obj.type === "string") {
        base = "";
      } else if (obj.type === "atom") {
        base = 1;
      } else if (obj.type === "function") {
        if (window.jQuery !== null && (obj.string === '$' || obj.string === 'jQuery') &&
            (typeof window.jQuery === 'function'))
          base = window.jQuery();
        else if (window._ !== null && (obj.string === '_') && (typeof window._ === 'function'))
          base = window._();
      }
      while (base !== null && context.length)
        base = base[context.pop().string];
      if (base !== null)
        gatherCompletions(base);
    }
    else {
      // If not, just look in the window object and any local scope
      // (reading into JS mode internals to get at the local and global variables)
      for (var v = token.state.localVars; v; v = v.next)
        maybeAdd(v.name);
      for (var v = token.state.globalVars; v; v = v.next)
        maybeAdd(v.name);
      gatherCompletions(window);
      _.each(keywords, maybeAdd);
    }
    return found;
  };
  var Pos = CodeMirror.Pos;

  function scriptHint(editor, keywords, getToken, options) {
    // Find the token at the cursor
    var cur = editor.getCursor(), token = getToken(editor, cur), tprop = token;
    token.state = CodeMirror.innerMode(editor.getMode(), token.state).state;

    // If it's not a 'word-style' token, ignore the token.
    if (!/^[\w$_]*$/.test(token.string)) {
      token = tprop = {start: cur.ch, end: cur.ch, string: "", state: token.state,
        type: token.string === "." ? "property" : null};
    }
    // If it is a property, find out what it is a property of.
    while (tprop.type === "property") {
      tprop = getToken(editor, Pos(cur.line, tprop.start));
      if (tprop.string !== ".")
        return;
      tprop = getToken(editor, Pos(cur.line, tprop.start));
      if (tprop.string === ')') {
        var level = 1;
        do {
          tprop = getToken(editor, Pos(cur.line, tprop.start));
          switch (tprop.string) {
            case ')':
              level++;
              break;
            case '(':
              level--;
              break;
            default:
              break;
          }
        } while (level > 0);
        tprop = getToken(editor, Pos(cur.line, tprop.start));
        if (tprop.type.indexOf("variable") === 0)
          tprop.type = "function";
        else
          return; // no clue
      }
      if (!context)
        var context = [];
      context.push(tprop);
    }
    return getCompletions(token, context, keywords, options);
  }

  var JavascriptCancelFunction = null;

  function showErrorState(err) {
    bkHelper.receiveEvaluationUpdate(err.modelOutput,
        {status: "ERROR", payload: err.payload},
        PLUGIN_NAME);
  }

  var JavaScript_0 = {
    pluginName: PLUGIN_NAME,
    cmMode: "javascript",
    background: "#FFE0F0",
    bgColor: "#EFDB52",
    fgColor: "#4A4A4A",
    borderColor: "",
    shortName: "Js",
    tooltip: "The native scripting language of the web",
    evaluate: function(code, modelOutput, refreshObj) {
      var deferred = bkHelper.newDeferred();
      var self = this;
      bkHelper.timeout(function () {
        // this is visible to JS code in cell
        var beakerObj = bkHelper.getBeakerObject();
        try {
          var progressObj = {
            type: "BeakerDisplay",
            innertype: "Progress",
            object: {
              message: "running...",
              startTime: new Date().getTime(),
              outputdata: []
            }
          };
          modelOutput.result = progressObj;
          if (refreshObj !== undefined)
            refreshObj.outputRefreshed();
          else
            bkHelper.refreshRootScope();

          beakerObj.setupBeakerObject(modelOutput);
          beakerObj.notebookToBeakerObject();
          window.beaker = beakerObj.beakerObj;
          try {
            if (LANGUAGE_VERSIONS[self.settings.languageVersion].babel) {
              code = Babel.transform(code, { plugins: ES2015Plugins }).code;
            } else {
              acorn.parse(code);
            }
          } catch (e) {
            showErrorState({
              modelOutput: modelOutput,
              payload: e.message
            });
            beakerObj.clearOutput();
            return deferred.reject();
          }
          // (0, eval) is an indirect eval call to evaluate in non-strict mode
          // more info: http://stackoverflow.com/questions/19357978/indirect-eval-call-in-strict-mode
          var output = (0, eval)(code);
          beakerObj.beakerObjectToNotebook();
          if ( typeof output === 'object' ) {
            if(typeof output.promise === 'object' && typeof output.promise.then === 'function') {
              output = output.promise;
            }
            if(typeof output.then === 'function') {
              JavascriptCancelFunction = function () {
                showErrorState({
                  modelOutput: modelOutput,
                  payload: 'cancelled...'
                });
                if (modelOutput !== undefined && modelOutput.object !== undefined)
                  modelOutput.elapsedTime = new Date().getTime() - modelOutput.object.startTime;
                JavascriptCancelFunction = null;
                beakerObj.beakerObjectToNotebook();
                if ( typeof output.reject === 'function') {
                  output.reject();
                  output = undefined;
                } else {
                  deferred.reject();
                }
              };
              output.then(function(o) {
                JavascriptCancelFunction = null;
                if (beakerObj.isCircularObject(o))
                  o = "ERROR: circular objects are not supported";
                else
                  o = beakerObj.transform(o);
                bkHelper.receiveEvaluationUpdate(modelOutput, {status: "FINISHED", payload: o}, PLUGIN_NAME);
                beakerObj.beakerObjectToNotebook();
                deferred.resolve(o);
                beakerObj.clearOutput();
              }, function(e) {
                JavascriptCancelFunction = null;
                bkHelper.receiveEvaluationUpdate(modelOutput,
                    {status: "ERROR", payload: e}, PLUGIN_NAME);
                console.log(e);
                beakerObj.beakerObjectToNotebook();
                var r;
                if (beakerObj.isCircularObject(modelOutput.result))
                  r = "ERROR: circular objects are not supported";
                else
                  r = beakerObj.transform(modelOutput.result);
                deferred.reject(r);
                beakerObj.clearOutput();
              });
            } else {
              if (!beakerObj.isBeakerObject(output) && beakerObj.isCircularObject(output))
                output = "ERROR: circular objects are not supported";
              else
                output = beakerObj.transform(output);
              bkHelper.receiveEvaluationUpdate(modelOutput, {status: "FINISHED", payload: output}, PLUGIN_NAME);
              deferred.resolve(output);
              beakerObj.clearOutput();
            }
          } else {
            bkHelper.receiveEvaluationUpdate(modelOutput, {status: "FINISHED", payload: output}, PLUGIN_NAME);
            deferred.resolve(output);
            beakerObj.clearOutput();
          }
        } catch (err) {
          // detect chrome or firefox stack traces
          var stack = [];
          try {
            if (err.fileName !== undefined) {
              var stackin = err.stack.split(/\n/);
              stack.push(err.name+': '+err.message);
              var stage = 0;
              for(var i=0; i<stackin.length; i++) {
                var line = stackin[i];
                if (line.indexOf(' > eval:')>=0) {
                  var a = line.indexOf('@');
                  var b = line.indexOf(' > eval:');
                  stage = 1;
                  stack.push('    at '+line.substring(0,a)+' <codecell>'+line.substring(b+7));
                } else if(stage==0) {
                  stack.push(line);
                } else
                  break;
              }
              if (stack.length>1)
                stack.pop();
            } else {
              var stackin = err.stack.split(/\n/);

              stack.push(stackin[0]);
              for(var i=1; i<stackin.length; i++) {
                var line = stackin[i];
                if (line.indexOf(' at eval (eval at <anonymous> ')>=0)
                    break;
                    if (line.indexOf(' (eval at <anonymous>')>=0 && line.indexOf(', <anonymous>:')>=0 ) {
                      var a = line.indexOf(' (eval at <anonymous>');
                          var b = line.indexOf(', <anonymous>:')+13;
                          var c = line.indexOf(')',b);
                          stack.push(line.substring(0,a)+' <codecell>'+line.substring(b,c));
                          } else
                          stack.push(line);
                          }
                          }
                          } catch(e) { }
                          showErrorState({
                            modelOutput: modelOutput,
                            payload: stack
                          });
                          console.log(stack);
                          beakerObj.clearOutput();
                          var r;
                          if (beakerObj.isCircularObject(modelOutput.result))
                          r = "ERROR: circular objects are not supported";
                          else
                          r = beakerObj.transform(modelOutput.result);
                          deferred.reject(r);
        }
      }, 0);
      return deferred.promise;
    },
    interrupt: function() {
      this.cancelExecution();
    },
    cancelExecution: function () {
      if (JavascriptCancelFunction) {
        JavascriptCancelFunction();
      }
    },

    autocomplete2: function(editor, options, cb) {
      var ret = scriptHint(editor, javascriptKeywords,
          function(e, cur) {
            return e.getTokenAt(cur);
          },
          options);
      cb(ret,undefined,true);
    },
    exit: function(cb) {
      this.cancelExecution();
      JavascriptCancelFunction = null;
    },
    spec: {
      languageVersion: {
        type: "settableEnum",
        name: "JavaScript Version",
        values: _.mapValues(LANGUAGE_VERSIONS, function(version) {return version.name;})}
    }
  };
  var JavaScript0 = function(settings) {
    if (!settings.view) {
      settings.view = {};
    }
    if (!settings.view.cm) {
      settings.view.cm = {};
    }
    settings.view.cm.mode = JavaScript_0.cmMode;
    settings.view.cm.background = JavaScript_0.background;
    this.settings = settings;

    if (!_.contains(_.keys(LANGUAGE_VERSIONS), this.settings.languageVersion)) {
      this.settings.languageVersion = getDefaultLanguageVersion();
    }
    this.perform = function(what) {
      var action = this.spec[what].action;
      this[action]();
    };
  };
  JavaScript0.prototype = JavaScript_0;

  exports.getEvaluatorFactory = function() {
    return bkHelper.getEvaluatorFactory(bkHelper.newPromise(JavaScript0));
  };
  exports.name = PLUGIN_NAME;
});
<|endoftext|>


<commit_before>#!/usr/bin/env node

let firebase = require("firebase-admin"),
    glob = require("glob"),
    yamljs = require("yamljs"),
    fs = require("fs-extra"),
    crypto = require('crypto'),
    metaMarked = require("meta-marked"),
    path = require("path"),
    moment = require("moment");

const { getCompilationQuarterValue, getInfoFromPath } = require('./deploy-helper')

let argv = require("optimist").usage("Compile & deploy video - DON'T USE IF YOU DON'T KNOW WHAT IT DOES\n" +
    "Usage: $0 -b [string]")
    .alias({"b": "branch"})
    .describe({
        "b": "branch",
        "l": "language",
        "q": "quarter",
        "m": "mode"
    })
    .demand(["b"])
    .argv;

let branch = argv.b,
    compile_language = argv.l || "*",
    compile_quarter = argv.q || getCompilationQuarterValue(),
    mode = argv.m || "sync";

let API_HOST = "https://sabbath-school.adventech.io/api/",
    MEDIA_HOST = "https://sabbath-school-media.adventech.io/",
    API_VERSION = "v1",
    SOURCE_DIR = "src/",
    SOURCE_VIDEO_FILE = "video.yml",
    SOURCE_COVER_FILE = "cover.png",
    DIST_DIR = "dist/api/" + API_VERSION + "/",
    FIREBASE_DATABASE_VIDEO = "/api/" + API_VERSION + "/video";

let db
if (branch.toLowerCase() === "master") {
    API_HOST = "https://sabbath-school.adventech.io/api/";
    MEDIA_HOST = "https://sabbath-school-media.adventech.io/";
    firebase.initializeApp({
        databaseURL: "https://blistering-inferno-8720.firebaseio.com",
        credential: firebase.credential.cert(require('./deploy-creds.json')),
        databaseAuthVariableOverride: {
            uid: "deploy"
        }
    });
    db = firebase.database();
} else if (branch.toLowerCase() === "stage") {
    API_HOST = "https://sabbath-school-stage.adventech.io/api/";
    MEDIA_HOST = "https://sabbath-school-media-stage.adventech.io/";
    firebase.initializeApp({
        databaseURL: "https://sabbath-school-stage.firebaseio.com",
        credential: firebase.credential.cert(require('./deploy-creds-stage.json')),
        databaseAuthVariableOverride: {
            uid: "deploy"
        }
    });
    db = firebase.database();
} else {
    firebase = {
        app: function () {
            return {
                delete: function () {}
            }
        }
    };
    db = {
        ref: function () {
            return {
                set: function () {},
                child: function () {
                    return {
                        set: function (a) {},
                        once: function () {}
                    }
                }
            }
        },
        goOffline: function () {}
    }
}

let videoAPI = async function (mode) {
    const allowedVideoItemKeys = ['title', 'src', 'thumbnail', 'target', 'duration']
    console.log('Deploying video API');

    let videos = glob.sync(`${SOURCE_DIR}/${compile_language}/${compile_quarter}/${SOURCE_VIDEO_FILE}`);
    let videoAPIJson = []

    let curlConfig = ""

    for (let video of videos) {
        let videoSource = yamljs.load(`${video}`),
            info = getInfoFromPath(video);

        for (let artist of videoSource.video) {
            let videoInfo = {
                artist: artist.artist,
                clips: []
            }

            if (artist.thumbnail) {
                videoInfo.thumbnail = artist.thumbnail
            }

            for (let clip of artist.clips) {
                if (!clip['target'] || !clip['src']) { continue }

                let videoItem = {
                    artist: videoInfo.artist
                }

                videoItem.id = crypto.createHash('sha256').update(artist.artist + clip['target'] + clip['src']).digest('hex');

                for (let k of Object.keys(clip)) {
                    if (allowedVideoItemKeys.indexOf(k) >= 0) {
                        videoItem[k] = clip[k]
                    }
                }

                let extname = path.extname(videoItem.src)

                if (!extname.length || extname.length <= 1) {
                    extname = ".mp4"
                }

                videoItem.src = `${MEDIA_HOST}video/${info.language}/${info.quarterly}/${videoItem.id}/${videoItem.id}${extname}`

                videoItem.targetIndex = videoItem.target.replace(/\//g, '-')

                if (!videoItem.thumbnail) {
                    if (artist.thumbnail && !/^http/.test(artist.thumbnail.trim())) {
                        artist.thumbnail = `${API_HOST}${API_VERSION}/images/${artist.thumbnail}`
                    }
                    videoItem.thumbnail = artist.thumbnail || `${API_HOST}${API_VERSION}/${info.language}/quarterlies/${info.quarterly}/${SOURCE_COVER_FILE}`
                }

                if (videoItem.duration) {
                    if (typeof videoItem.duration === 'number') {
                        videoItem.duration = moment("2015-01-01").startOf('day').seconds(videoItem.duration).format("H:mm:ss").replace(/^[0:]+(?=\d[\d:]{3})/, '');
                    }
                }

                if (!videoItem.title) {
                    let videoItemInfo = getInfoFromPath(`src/${videoItem.target}`)
                    if (!videoItemInfo.lesson) {
                        continue
                    }

                    if (videoItemInfo.day) {
                        let read = metaMarked(fs.readFileSync(`${SOURCE_DIR}${videoItemInfo.language}/${videoItemInfo.quarterly}/${videoItemInfo.lesson}/${videoItemInfo.day}.md`, "utf-8"))
                        videoItem.title = read.meta.title
                    } else {
                        let lesson = yamljs.load(`${SOURCE_DIR}${videoItemInfo.language}/${videoItemInfo.quarterly}/${videoItemInfo.lesson}/info.yml`)
                        videoItem.title = lesson.title
                    }
                }

                videoInfo.clips.push(videoItem)

                if (mode === "keep" && fs.pathExistsSync(`video/video/${info.language}/${info.quarterly}/${videoItem.id}/${videoItem.id}${extname}`)) {
                    let stats = fs.statSync(`video/video/${info.language}/${info.quarterly}/${videoItem.id}/${videoItem.id}${extname}`);
                    if (stats.size > 0) {
                        fs.outputFileSync(`video/video/${info.language}/${info.quarterly}/${videoItem.id}/.keep`, "");
                    }
                }

                if (mode === "gen" && !fs.pathExistsSync(`video/video/${info.language}/${info.quarterly}/${videoItem.id}/`)) {
                    curlConfig += `
url = "${clip.src}"
output = "video/video/${info.language}/${info.quarterly}/${videoItem.id}/${videoItem.id}${extname}"
-C -
--create-dirs
-L
`
                }
            }

            if (videoInfo.clips.length) {

                videoInfo.clips = videoInfo.clips.sort(function(a, b){
                    if (a.targetIndex < b.targetIndex) {
                        return a.targetIndex.length < b.targetIndex.length ? 1 : -1;
                    }

                    if (a.targetIndex > b.targetIndex) {
                        return a.targetIndex.length > b.targetIndex.length ? -1 : 1;
                    }
                    return 0;
                })

                videoAPIJson.push(videoInfo)
            }
        }

        if (mode === "sync") {
            await db.ref(FIREBASE_DATABASE_VIDEO).child(`${info.language}-${info.quarterly}`).set(videoAPIJson);

            if (videoAPIJson.length) {
                fs.outputFileSync(`${DIST_DIR}${info.language}/quarterlies/${info.quarterly}/video.json`, JSON.stringify(videoAPIJson));
            }
        }
    }

    if (mode === "gen" && curlConfig.trim().length > 1) {
        fs.outputFileSync(`curl-config.txt`, curlConfig);
    }
};

((async function () {
    try {
        await videoAPI(mode);
    } catch (e) {
        console.error(e)
    }
})()).then(() => {
    db.goOffline();
    firebase.app().delete();
});<commit_msg>Adding start & end date to<commit_after>#!/usr/bin/env node

let firebase = require("firebase-admin"),
    glob = require("glob"),
    yamljs = require("yamljs"),
    fs = require("fs-extra"),
    crypto = require('crypto'),
    metaMarked = require("meta-marked"),
    path = require("path"),
    moment = require("moment");

const { getCompilationQuarterValue, getInfoFromPath } = require('./deploy-helper')

let argv = require("optimist").usage("Compile & deploy video - DON'T USE IF YOU DON'T KNOW WHAT IT DOES\n" +
    "Usage: $0 -b [string]")
    .alias({"b": "branch"})
    .describe({
        "b": "branch",
        "l": "language",
        "q": "quarter",
        "m": "mode"
    })
    .demand(["b"])
    .argv;

let branch = argv.b,
    compile_language = argv.l || "*",
    compile_quarter = argv.q || getCompilationQuarterValue(),
    mode = argv.m || "sync";

let API_HOST = "https://sabbath-school.adventech.io/api/",
    MEDIA_HOST = "https://sabbath-school-media.adventech.io/",
    API_VERSION = "v1",
    SOURCE_DIR = "src/",
    SOURCE_VIDEO_FILE = "video.yml",
    SOURCE_COVER_FILE = "cover.png",
    DIST_DIR = "dist/api/" + API_VERSION + "/",
    FIREBASE_DATABASE_VIDEO = "/api/" + API_VERSION + "/video";

let db
if (branch.toLowerCase() === "master") {
    API_HOST = "https://sabbath-school.adventech.io/api/";
    MEDIA_HOST = "https://sabbath-school-media.adventech.io/";
    firebase.initializeApp({
        databaseURL: "https://blistering-inferno-8720.firebaseio.com",
        credential: firebase.credential.cert(require('./deploy-creds.json')),
        databaseAuthVariableOverride: {
            uid: "deploy"
        }
    });
    db = firebase.database();
} else if (branch.toLowerCase() === "stage") {
    API_HOST = "https://sabbath-school-stage.adventech.io/api/";
    MEDIA_HOST = "https://sabbath-school-media-stage.adventech.io/";
    firebase.initializeApp({
        databaseURL: "https://sabbath-school-stage.firebaseio.com",
        credential: firebase.credential.cert(require('./deploy-creds-stage.json')),
        databaseAuthVariableOverride: {
            uid: "deploy"
        }
    });
    db = firebase.database();
} else {
    firebase = {
        app: function () {
            return {
                delete: function () {}
            }
        }
    };
    db = {
        ref: function () {
            return {
                set: function () {},
                child: function () {
                    return {
                        set: function (a) {},
                        once: function () {}
                    }
                }
            }
        },
        goOffline: function () {}
    }
}

let videoAPI = async function (mode) {
    const allowedVideoItemKeys = ['title', 'src', 'thumbnail', 'target', 'duration']
    console.log('Deploying video API');

    let videos = glob.sync(`${SOURCE_DIR}/${compile_language}/${compile_quarter}/${SOURCE_VIDEO_FILE}`);
    let videoAPIJson = []

    let curlConfig = ""

    for (let video of videos) {
        let videoSource = yamljs.load(`${video}`),
            info = getInfoFromPath(video);

        for (let artist of videoSource.video) {
            let videoInfo = {
                artist: artist.artist,
                clips: []
            }

            if (artist.thumbnail) {
                videoInfo.thumbnail = artist.thumbnail
            }

            for (let clip of artist.clips) {
                if (!clip['target'] || !clip['src']) { continue }

                let videoItem = {
                    artist: videoInfo.artist
                }

                videoItem.id = crypto.createHash('sha256').update(artist.artist + clip['target'] + clip['src']).digest('hex');

                for (let k of Object.keys(clip)) {
                    if (allowedVideoItemKeys.indexOf(k) >= 0) {
                        videoItem[k] = clip[k]
                    }
                }

                let extname = path.extname(videoItem.src)

                if (!extname.length || extname.length <= 1) {
                    extname = ".mp4"
                }

                videoItem.src = `${MEDIA_HOST}video/${info.language}/${info.quarterly}/${videoItem.id}/${videoItem.id}${extname}`

                videoItem.targetIndex = videoItem.target.replace(/\//g, '-')

                if (!videoItem.thumbnail) {
                    if (artist.thumbnail && !/^http/.test(artist.thumbnail.trim())) {
                        artist.thumbnail = `${API_HOST}${API_VERSION}/images/${artist.thumbnail}`
                    }
                    videoItem.thumbnail = artist.thumbnail || `${API_HOST}${API_VERSION}/${info.language}/quarterlies/${info.quarterly}/${SOURCE_COVER_FILE}`
                }

                if (videoItem.duration) {
                    if (typeof videoItem.duration === 'number') {
                        videoItem.duration = moment("2015-01-01").startOf('day').seconds(videoItem.duration).format("H:mm:ss").replace(/^[0:]+(?=\d[\d:]{3})/, '');
                    }
                }

                let videoItemInfo = getInfoFromPath(`src/${videoItem.target}`)
                if (!videoItemInfo.lesson) {
                    continue
                }

                if (videoItemInfo.day) {
                    let read = metaMarked(fs.readFileSync(`${SOURCE_DIR}${videoItemInfo.language}/${videoItemInfo.quarterly}/${videoItemInfo.lesson}/${videoItemInfo.day}.md`, "utf-8"))
                    videoItem.title = videoItem.title ? videoItem.title : read.meta.title
                    videoItem.start_date = read.meta.date
                    videoItem.end_date = read.meta.date
                } else {
                    let lesson = yamljs.load(`${SOURCE_DIR}${videoItemInfo.language}/${videoItemInfo.quarterly}/${videoItemInfo.lesson}/info.yml`)
                    videoItem.title = videoItem.title ? videoItem.title : lesson.title
                    videoItem.start_date = lesson.start_date
                    videoItem.end_date = lesson.end_date
                }

                videoInfo.clips.push(videoItem)

                if (mode === "keep" && fs.pathExistsSync(`video/video/${info.language}/${info.quarterly}/${videoItem.id}/${videoItem.id}${extname}`)) {
                    let stats = fs.statSync(`video/video/${info.language}/${info.quarterly}/${videoItem.id}/${videoItem.id}${extname}`);
                    if (stats.size > 0) {
                        fs.outputFileSync(`video/video/${info.language}/${info.quarterly}/${videoItem.id}/.keep`, "");
                    }
                }

                if (mode === "gen" && !fs.pathExistsSync(`video/video/${info.language}/${info.quarterly}/${videoItem.id}/`)) {
                    curlConfig += `
url = "${clip.src}"
output = "video/video/${info.language}/${info.quarterly}/${videoItem.id}/${videoItem.id}${extname}"
-C -
--create-dirs
-L
`
                }
            }

            if (videoInfo.clips.length) {

                videoInfo.clips = videoInfo.clips.sort(function(a, b){
                    if (a.targetIndex < b.targetIndex) {
                        return a.targetIndex.length < b.targetIndex.length ? 1 : -1;
                    }

                    if (a.targetIndex > b.targetIndex) {
                        return a.targetIndex.length > b.targetIndex.length ? -1 : 1;
                    }
                    return 0;
                })

                videoAPIJson.push(videoInfo)
            }
        }

        if (mode === "sync") {
            await db.ref(FIREBASE_DATABASE_VIDEO).child(`${info.language}-${info.quarterly}`).set(videoAPIJson);

            if (videoAPIJson.length) {
                fs.outputFileSync(`${DIST_DIR}${info.language}/quarterlies/${info.quarterly}/video.json`, JSON.stringify(videoAPIJson));
            }
        }
    }

    if (mode === "gen" && curlConfig.trim().length > 1) {
        fs.outputFileSync(`curl-config.txt`, curlConfig);
    }
};

((async function () {
    try {
        await videoAPI(mode);
    } catch (e) {
        console.error(e)
    }
})()).then(() => {
    db.goOffline();
    firebase.app().delete();
});<|endoftext|>


<commit_before>// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.

define([
    "widgets/js/widget",
    "jquery",
    'notebook/js/outputarea',
], function(widget, $, outputarea){

    var OutputView = widget.DOMWidgetView.extend({
        initialize: function (parameters) {
            // Public constructor
            OutputView.__super__.initialize.apply(this, [parameters]);
            this.model.on('msg:custom', this._handle_route_msg, this);
        },

        render: function(){
            // Called when view is rendered.
            this.output_area = new outputarea.OutputArea({
                selector: this.$el,
                prompt_area: false,
                events: this.model.widget_manager.notebook.events,
                keyboard_manager: this.model.widget_manager.keyboard_manager });
        },

        _handle_route_msg: function(content) {
            var cell = this.options.cell;
            if (content && cell) {
                if (content.method == 'push') {
                    cell.push_output_area(this.output_area);
                } else if (content.method == 'pop') {
                    cell.pop_output_area(this.output_area);
                }
            }
        },
    });

    return {
        'OutputView': OutputView,
    };
});
<commit_msg>Make output widget reactive.<commit_after>// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.

define([
    "widgets/js/widget",
    "jquery",
    'notebook/js/outputarea',
], function(widget, $, outputarea){

    var OutputView = widget.DOMWidgetView.extend({
        initialize: function (parameters) {
            // Public constructor
            OutputView.__super__.initialize.apply(this, [parameters]);
            this.model.on('msg:custom', this._handle_route_msg, this);
        },

        render: function(){
            // Called when view is rendered.
            this.output_area = new outputarea.OutputArea({
                selector: this.$el,
                prompt_area: false,
                events: this.model.widget_manager.notebook.events,
                keyboard_manager: this.model.widget_manager.keyboard_manager });

            // Make output area reactive.
            var that = this;
            this.output_area.element.on('changed', function() {
                that.model.set('contents', that.output_area.element.html());
            });
            this.model.on('change:contents', function(){
                var html = this.model.get('contents');
                if (this.output_area.element.html() != html) {
                    this.output_area.element.html(html);
                }
            }, this);

            // Set initial contents.
            this.output_area.element.html(this.model.get('contents'));
        },

        _handle_route_msg: function(content) {
            var cell = this.options.cell;
            if (content && cell) {
                if (content.method == 'push') {
                    cell.push_output_area(this.output_area);
                } else if (content.method == 'pop') {
                    cell.pop_output_area(this.output_area);
                }
            }
        },
    });

    return {
        'OutputView': OutputView,
    };
});
<|endoftext|>