<commit_before>(function($){
    $.fn.bootstrapTransfer = function(options) {
        var settings = $.extend({}, $.fn.bootstrapTransfer.defaults, options);
        var _this;
        /* #=============================================================================== */
        /* # Expose public functions */
        /* #=============================================================================== */
        this.populate = function(input) { _this.populate(input); };
        this.set_values = function(values) { _this.set_values(values); };
        this.get_values = function() { return _this.get_values(); };
        return this.each(function(){
            _this = $(this);
            /* #=============================================================================== */
            /* # Add widget markup */
            /* #=============================================================================== */
            _this.append($.fn.bootstrapTransfer.defaults.template);
            _this.addClass("bootstrap-transfer-container");
            /* #=============================================================================== */
            /* # Initialize internal variables */
            /* #=============================================================================== */
            _this.$filter_input = _this.find('.filter-input');
            _this.$remaining_select = _this.find('select.remaining');
            _this.$target_select = _this.find('select.target');
            _this.$add_btn = _this.find('.selector-add');
            _this.$remove_btn = _this.find('.selector-remove');
            _this.$choose_all_btn = _this.find('.selector-chooseall');
            _this.$clear_all_btn = _this.find('.selector-clearall');
            _this._remaining_list = [];
            _this._target_list = [];
            /* #=============================================================================== */
            /* # Apply settings */
            /* #=============================================================================== */
            /* target_id */
            if (settings.target_id != '') _this.$target_select.attr('id', settings.target_id);
            /* height */
            _this.find('select.remaining').css('height', settings.height + 'em');
            _this.find('select.target').css('height', settings.height + 4 + 'em');
            /* #=============================================================================== */
            /* # Wire internal events */
            /* #=============================================================================== */
            _this.$add_btn.click(function(){
                _this.move_elems(_this.$remaining_select.val(), false, true);
            });
            _this.$remove_btn.click(function(){
                _this.move_elems(_this.$target_select.val(), true, false);
            });
            _this.$choose_all_btn.click(function(){
                _this.move_all(false, true);
            });
            _this.$clear_all_btn.click(function(){
                _this.move_all(true, false);
            });
            _this.$filter_input.keyup(function(){
                _this.update_lists(true);
            });
            /* #=============================================================================== */
            /* # Implement public functions */
            /* #=============================================================================== */
            _this.populate = function(input) {
                // input: [{value:_, content:_}]
                _this.$filter_input.val('');
                for (var i in input) {
                    var e = input[i];
                    _this._remaining_list.push([{value:e.value, content:e.content}, true]);
                    _this._target_list.push([{value:e.value, content:e.content}, false]);
                }
                _this.update_lists(true);
            };
            _this.set_values = function(values) {
                _this.move_elems(values, false, true);
            };
            _this.get_values = function(){
                return _this.get_internal(_this.$target_select);
            };
            /* #=============================================================================== */
            /* # Implement private functions */
            /* #=============================================================================== */
            _this.get_internal = function(selector) {
                var res = [];
                selector.find('option').each(function() {
                    res.push($(this).val());
                })
                return res;
            };
            _this.to_dict = function(list) {
                var res = {};
                for (var i in list) res[list[i]] = true;
                return res;
            }
            _this.update_lists = function(force_hilite_off) {
                var old;
                if (!force_hilite_off) {
                    old = [_this.to_dict(_this.get_internal(_this.$remaining_select)),
                           _this.to_dict(_this.get_internal(_this.$target_select))];
                }
                _this.$remaining_select.empty();
                _this.$target_select.empty();
                var lists = [_this._remaining_list, _this._target_list];
                var source = [_this.$remaining_select, _this.$target_select];
                for (var i in lists) {
                    for (var j in lists[i]) {
                        var e = lists[i][j];
                        if (e[1]) {
                            var selected = '';
                            if (!force_hilite_off && settings.hilite_selection && !old[i].hasOwnProperty(e[0].value)) {
                                selected = 'selected="selected"';
                            }
                            source[i].append('<option ' + selected + 'value=' + e[0].value + '>' + e[0].content + '</option>');
                        }
                    }
                }
                _this.$remaining_select.find('option').each(function() {
                    var inner = _this.$filter_input.val().toLowerCase();
                    var outer = $(this).html().toLowerCase();
                    if (outer.indexOf(inner) == -1) {
                        $(this).remove();
                    }
                })
            };
            _this.move_elems = function(values, b1, b2) {
                for (var i in values) {
                    val = values[i];
                    for (var j in _this._remaining_list) {
                        var e = _this._remaining_list[j];
                        if (e[0].value == val) {
                            e[1] = b1;
                            _this._target_list[j][1] = b2;
                        }
                    }
                }
                _this.update_lists(false);
            };
            _this.move_all = function(b1, b2) {
                for (var i in _this._remaining_list) {
                    _this._remaining_list[i][1] = b1;
                    _this._target_list[i][1] = b2;
                }
                _this.update_lists(false);
            };
            _this.data('bootstrapTransfer', _this);
            return _this;
        });
    };
    $.fn.bootstrapTransfer.defaults = {
        'template':
            '
\

\

\

\

\

Not Selected
\

\

\

\
                        <form class="form">\

\

\

\
                                \

\
                              <input class="form-control filter-input form-control" type="text" placeholder="Search..." />\

\

\
                        </form>\

\

\

\
                      <select class="filtered remaining form-control" multiple=true></select>\

\

\
                      <btn class="btn btn-default selector-chooseall">\
                        Choose All\
                        \
                      </btn>\

\

\

\

\

\
                    <btn class="btn btn-default selector selector-add">\
                      \
                    </btn>\
                    <btn class="btn btn-default selector selector-remove">\
                      \
                    </btn>\

\

\

\

\

\

Selected
\

\

\
                      <select class="filtered target form-control" multiple=true></select>\

\

\
                        <btn class="btn btn-default selector-clearall">\
                          \
                          Clear All\
                        </btn>\

\

\

\

\

',
        'height': 15,
        'hilite_selection': true,
        'target_id': ''
    }
})(jQuery);<commit_msg>remove form tag on bootstrap transfer to prevent nested forms<commit_after>(function($){
    $.fn.bootstrapTransfer = function(options) {
        var settings = $.extend({}, $.fn.bootstrapTransfer.defaults, options);
        var _this;
        /* #=============================================================================== */
        /* # Expose public functions */
        /* #=============================================================================== */
        this.populate = function(input) { _this.populate(input); };
        this.set_values = function(values) { _this.set_values(values); };
        this.get_values = function() { return _this.get_values(); };
        return this.each(function(){
            _this = $(this);
            /* #=============================================================================== */
            /* # Add widget markup */
            /* #=============================================================================== */
            _this.append($.fn.bootstrapTransfer.defaults.template);
            _this.addClass("bootstrap-transfer-container");
            /* #=============================================================================== */
            /* # Initialize internal variables */
            /* #=============================================================================== */
            _this.$filter_input = _this.find('.filter-input');
            _this.$remaining_select = _this.find('select.remaining');
            _this.$target_select = _this.find('select.target');
            _this.$add_btn = _this.find('.selector-add');
            _this.$remove_btn = _this.find('.selector-remove');
            _this.$choose_all_btn = _this.find('.selector-chooseall');
            _this.$clear_all_btn = _this.find('.selector-clearall');
            _this._remaining_list = [];
            _this._target_list = [];
            /* #=============================================================================== */
            /* # Apply settings */
            /* #=============================================================================== */
            /* target_id */
            if (settings.target_id != '') _this.$target_select.attr('id', settings.target_id);
            /* height */
            _this.find('select.remaining').css('height', settings.height + 'em');
            _this.find('select.target').css('height', settings.height + 4 + 'em');
            /* #=============================================================================== */
            /* # Wire internal events */
            /* #=============================================================================== */
            _this.$add_btn.click(function(){
                _this.move_elems(_this.$remaining_select.val(), false, true);
            });
            _this.$remove_btn.click(function(){
                _this.move_elems(_this.$target_select.val(), true, false);
            });
            _this.$choose_all_btn.click(function(){
                _this.move_all(false, true);
            });
            _this.$clear_all_btn.click(function(){
                _this.move_all(true, false);
            });
            _this.$filter_input.keyup(function(){
                _this.update_lists(true);
            });
            /* #=============================================================================== */
            /* # Implement public functions */
            /* #=============================================================================== */
            _this.populate = function(input) {
                // input: [{value:_, content:_}]
                _this.$filter_input.val('');
                for (var i in input) {
                    var e = input[i];
                    _this._remaining_list.push([{value:e.value, content:e.content}, true]);
                    _this._target_list.push([{value:e.value, content:e.content}, false]);
                }
                _this.update_lists(true);
            };
            _this.set_values = function(values) {
                _this.move_elems(values, false, true);
            };
            _this.get_values = function(){
                return _this.get_internal(_this.$target_select);
            };
            /* #=============================================================================== */
            /* # Implement private functions */
            /* #=============================================================================== */
            _this.get_internal = function(selector) {
                var res = [];
                selector.find('option').each(function() {
                    res.push($(this).val());
                })
                return res;
            };
            _this.to_dict = function(list) {
                var res = {};
                for (var i in list) res[list[i]] = true;
                return res;
            }
            _this.update_lists = function(force_hilite_off) {
                var old;
                if (!force_hilite_off) {
                    old = [_this.to_dict(_this.get_internal(_this.$remaining_select)),
                           _this.to_dict(_this.get_internal(_this.$target_select))];
                }
                _this.$remaining_select.empty();
                _this.$target_select.empty();
                var lists = [_this._remaining_list, _this._target_list];
                var source = [_this.$remaining_select, _this.$target_select];
                for (var i in lists) {
                    for (var j in lists[i]) {
                        var e = lists[i][j];
                        if (e[1]) {
                            var selected = '';
                            if (!force_hilite_off && settings.hilite_selection && !old[i].hasOwnProperty(e[0].value)) {
                                selected = 'selected="selected"';
                            }
                            source[i].append('<option ' + selected + 'value=' + e[0].value + '>' + e[0].content + '</option>');
                        }
                    }
                }
                _this.$remaining_select.find('option').each(function() {
                    var inner = _this.$filter_input.val().toLowerCase();
                    var outer = $(this).html().toLowerCase();
                    if (outer.indexOf(inner) == -1) {
                        $(this).remove();
                    }
                })
            };
            _this.move_elems = function(values, b1, b2) {
                for (var i in values) {
                    val = values[i];
                    for (var j in _this._remaining_list) {
                        var e = _this._remaining_list[j];
                        if (e[0].value == val) {
                            e[1] = b1;
                            _this._target_list[j][1] = b2;
                        }
                    }
                }
                _this.update_lists(false);
            };
            _this.move_all = function(b1, b2) {
                for (var i in _this._remaining_list) {
                    _this._remaining_list[i][1] = b1;
                    _this._target_list[i][1] = b2;
                }
                _this.update_lists(false);
            };
            _this.data('bootstrapTransfer', _this);
            return _this;
        });
    };
    $.fn.bootstrapTransfer.defaults = {
        'template':
            '
\

\

\

\

\

Not Selected
\

\

\

\

\

\

\
                                \

\
                              <input class="form-control filter-input form-control" type="text" placeholder="Search..." />\

\

\

\

\

\
                      <select class="filtered remaining form-control" multiple=true></select>\

\

\
                      <btn class="btn btn-default selector-chooseall">\
                        Choose All\
                        \
                      </btn>\

\

\

\

\

\
                    <btn class="btn btn-default selector selector-add">\
                      \
                    </btn>\
                    <btn class="btn btn-default selector selector-remove">\
                      \
                    </btn>\

\

\

\

\

\

Selected
\

\

\
                      <select class="filtered target form-control" multiple=true></select>\

\

\
                        <btn class="btn btn-default selector-clearall">\
                          \
                          Clear All\
                        </btn>\

\

\

\

\

',
        'height': 15,
        'hilite_selection': true,
        'target_id': ''
    }
})(jQuery);<|endoftext|>


<commit_before>$(document).ready(function(){

  var intervalNewCommits;
  intervalNewCommits = setInterval(function() { Repo.getNewCommits() }, 3000);
  $(".all_repos").ready(function(){
    $('.add_button').click(function(e){
      e.preventDefault();
      View.toggle_new_btn();
    });

    $('.btn-delete').on('ajax:success', function(data) {
      $(this).parent().remove();
    });
  });

  $(".commits").ready(function(){
    clearInterval(intervalNewCommits);
  });

});
<commit_msg>moved function in js<commit_after>$(document).ready(function(){
    $('.add_button').click(function(e){
      e.preventDefault();
      View.toggle_new_btn();
    });

  var intervalNewCommits;
  intervalNewCommits = setInterval(function() { Repo.getNewCommits() }, 3000);
  $(".all_repos").ready(function(){

    $('.btn-delete').on('ajax:success', function(data) {
      $(this).parent().remove();
    });
  });

  $(".commits").ready(function(){
    clearInterval(intervalNewCommits);
  });

});
<|endoftext|>


<commit_before>//= require repositories/import/records_importer.js

/*
  global animateSpinner repositoryRecordsImporter getParam
  RepositoryDatatable PerfectScrollbar HelperModule
*/

(function(global) {
  'use strict';

  global.pageReload = function() {
    animateSpinner();
    location.reload();
  };

  function handleErrorSubmit(XHR) {
    var formGroup = $('#form-records-file').find('.form-group');
    formGroup.addClass('has-error');
    formGroup.find('.help-block').remove();
    formGroup.append(
      '' + XHR.responseJSON.message + ''
    );
  }

  function handleSuccessfulSubmit(data) {
    $('#modal-import-records').modal('hide');
    $(data.html).appendTo('body').promise().done(function() {
      $('#parse-records-modal')
        .modal('show')
        .on('hidden.bs.modal', function() {
          animateSpinner();
          location.reload();
        });
      repositoryRecordsImporter();
    });
  }

  function initParseRecordsModal() {
    var form = $('#form-records-file');
    var submitBtn = form.find('input[type="submit"]');
    submitBtn.on('click', function(event) {
      var data = new FormData();
      event.preventDefault();
      event.stopPropagation();
      data.append('file', document.getElementById('file').files[0]);
      data.append('team_id', document.getElementById('team_id').value);
      $.ajax({
        type: 'POST',
        url: form.attr('action'),
        data: data,
        success: handleSuccessfulSubmit,
        error: handleErrorSubmit,
        processData: false,
        contentType: false
      });
    });
  }

  function initImportRecordsModal() {
    $('#importRecordsButton').off().on('click', function() {
      $('#modal-import-records').modal('show');
      initParseRecordsModal();
    });
  }

  function loadRepositoryTab() {
    var param = getParam('repository');
    $('#repository-tabs a').on('click', function(e) {
      var pane = $(this);
      e.preventDefault();
      $.ajax({
        url: $(this).attr('data-url'),
        type: 'GET',
        dataType: 'json',
        success: function(data) {
          var tabBody = $(pane.context.hash).find('.tab-content-body');
          tabBody.html(data.html);
          pane.tab('show').promise().done(function(el) {
            initImportRecordsModal();
            RepositoryDatatable.destroy();
            RepositoryDatatable.init(el.attr('data-repo-table'));
          });
        },
        error: function() {
          // TODO
        }
      });
    });

    // on page load
    if (param) {
      // load selected tab
      $('a[href="#custom_repo_' + param + '"]').click();
    } else {
      // load first tab content
      $('#repository-tabs a:first').click();
    }

    // clean tab content
    $('a[data-toggle="tab"]').on('hide.bs.tab', function() {
      $('.tab-content-body').html('');
    });
  }

  function initShareModal() {
    var form = $('.share-repo-modal').find('form');
    var sharedCBs = form.find("input[name='share_team_ids[]']");
    var permissionCBs = form.find("input[name='write_permissions[]']");
    var permissionChanges = form.find("input[name='permission_changes']");
    var submitBtn = form.find('input[type="submit"]');
    var selectAllCheckbox = form.find('.all-teams .simple-checkbox');

    form.find('.teams-list').find('input.simple-checkbox, .permission-selector')
      .toggleClass('hidden', selectAllCheckbox.is(':checked'));
    form.find('.all-teams .trigger-checkbox')
      .toggleClass('hidden', !selectAllCheckbox.is(':checked'))
      .attr('disabled', !selectAllCheckbox.is(':checked'));

    selectAllCheckbox.change(function() {
      form.find('.teams-list').find('input.simple-checkbox, .permission-selector')
        .toggleClass('hidden', this.checked);
      form.find('.all-teams .trigger-checkbox').toggleClass('hidden', !this.checked)
        .attr('disabled', !this.checked);
    });

    sharedCBs.change(function() {
      $('#editable_' + this.value).toggleClass('hidden', !this.checked)
        .attr('disabled', !this.checked);
    });

    new PerfectScrollbar(form.find('.teams-list')[0]);

    permissionCBs.change(function() {
      var changes = JSON.parse(permissionChanges.val());
      changes[this.value] = 'true';
      permissionChanges.val(JSON.stringify(changes));
    });

    submitBtn.on('click', function(event) {
      event.preventDefault();
      $.ajax({
        type: 'POST',
        url: form.attr('action'),
        data: form.serialize(),
        success: function(data) {
          if (data.warnings) {
            alert(data.warnings);
          }
          $(`#slide-panel li.active .repository-share-status,
             #repository-toolbar .repository-share-status
          `).toggleClass('hidden', !data.status);
          HelperModule.flashAlertMsg(form.data('success-message'), 'success');
          $('.share-repo-modal').modal('hide');
        },
        error: function(data) {
          alert(data.responseJSON.errors);
          $('.share-repo-modal').modal('hide');
        }
      });
    });
  }

  $('#shareRepoBtn').on('ajax:success', function() {
    initShareModal();
  });

  $('.create-new-repository').initializeModal('#create-repo-modal');
  loadRepositoryTab();
  initImportRecordsModal();
}(window));
<commit_msg>Add check for existing elements<commit_after>//= require repositories/import/records_importer.js

/*
  global animateSpinner repositoryRecordsImporter getParam
  RepositoryDatatable PerfectScrollbar HelperModule
*/

(function(global) {
  'use strict';

  global.pageReload = function() {
    animateSpinner();
    location.reload();
  };

  function handleErrorSubmit(XHR) {
    var formGroup = $('#form-records-file').find('.form-group');
    formGroup.addClass('has-error');
    formGroup.find('.help-block').remove();
    formGroup.append(
      '' + XHR.responseJSON.message + ''
    );
  }

  function handleSuccessfulSubmit(data) {
    $('#modal-import-records').modal('hide');
    $(data.html).appendTo('body').promise().done(function() {
      $('#parse-records-modal')
        .modal('show')
        .on('hidden.bs.modal', function() {
          animateSpinner();
          location.reload();
        });
      repositoryRecordsImporter();
    });
  }

  function initParseRecordsModal() {
    var form = $('#form-records-file');
    var submitBtn = form.find('input[type="submit"]');
    submitBtn.on('click', function(event) {
      var data = new FormData();
      event.preventDefault();
      event.stopPropagation();
      data.append('file', document.getElementById('file').files[0]);
      data.append('team_id', document.getElementById('team_id').value);
      $.ajax({
        type: 'POST',
        url: form.attr('action'),
        data: data,
        success: handleSuccessfulSubmit,
        error: handleErrorSubmit,
        processData: false,
        contentType: false
      });
    });
  }

  function initImportRecordsModal() {
    $('#importRecordsButton').off().on('click', function() {
      $('#modal-import-records').modal('show');
      initParseRecordsModal();
    });
  }

  function loadRepositoryTab() {
    var param = getParam('repository');
    $('#repository-tabs a').on('click', function(e) {
      var pane = $(this);
      e.preventDefault();
      $.ajax({
        url: $(this).attr('data-url'),
        type: 'GET',
        dataType: 'json',
        success: function(data) {
          var tabBody = $(pane.context.hash).find('.tab-content-body');
          tabBody.html(data.html);
          pane.tab('show').promise().done(function(el) {
            initImportRecordsModal();
            RepositoryDatatable.destroy();
            RepositoryDatatable.init(el.attr('data-repo-table'));
          });
        },
        error: function() {
          // TODO
        }
      });
    });

    // on page load
    if (param) {
      // load selected tab
      $('a[href="#custom_repo_' + param + '"]').click();
    } else {
      // load first tab content
      $('#repository-tabs a:first').click();
    }

    // clean tab content
    $('a[data-toggle="tab"]').on('hide.bs.tab', function() {
      $('.tab-content-body').html('');
    });
  }

  function initShareModal() {
    var form = $('.share-repo-modal').find('form');
    var sharedCBs = form.find("input[name='share_team_ids[]']");
    var permissionCBs = form.find("input[name='write_permissions[]']");
    var permissionChanges = form.find("input[name='permission_changes']");
    var submitBtn = form.find('input[type="submit"]');
    var selectAllCheckbox = form.find('.all-teams .simple-checkbox');

    form.find('.teams-list').find('input.simple-checkbox, .permission-selector')
      .toggleClass('hidden', selectAllCheckbox.is(':checked'));
    form.find('.all-teams .trigger-checkbox')
      .toggleClass('hidden', !selectAllCheckbox.is(':checked'))
      .attr('disabled', !selectAllCheckbox.is(':checked'));

    selectAllCheckbox.change(function() {
      form.find('.teams-list').find('input.simple-checkbox, .permission-selector')
        .toggleClass('hidden', this.checked);
      form.find('.all-teams .trigger-checkbox').toggleClass('hidden', !this.checked)
        .attr('disabled', !this.checked);
    });

    sharedCBs.change(function() {
      $('#editable_' + this.value).toggleClass('hidden', !this.checked)
        .attr('disabled', !this.checked);
    });

    if (form.find('.teams-list').length) new PerfectScrollbar(form.find('.teams-list')[0]);

    permissionCBs.change(function() {
      var changes = JSON.parse(permissionChanges.val());
      changes[this.value] = 'true';
      permissionChanges.val(JSON.stringify(changes));
    });

    submitBtn.on('click', function(event) {
      event.preventDefault();
      $.ajax({
        type: 'POST',
        url: form.attr('action'),
        data: form.serialize(),
        success: function(data) {
          if (data.warnings) {
            alert(data.warnings);
          }
          $(`#slide-panel li.active .repository-share-status,
             #repository-toolbar .repository-share-status
          `).toggleClass('hidden', !data.status);
          HelperModule.flashAlertMsg(form.data('success-message'), 'success');
          $('.share-repo-modal').modal('hide');
        },
        error: function(data) {
          alert(data.responseJSON.errors);
          $('.share-repo-modal').modal('hide');
        }
      });
    });
  }

  $('#shareRepoBtn').on('ajax:success', function() {
    initShareModal();
  });

  $('.create-new-repository').initializeModal('#create-repo-modal');
  loadRepositoryTab();
  initImportRecordsModal();
}(window));
<|endoftext|>


<commit_before>TrelloPowerUp.initialize({
  'card-badges' : function(t, card) {
    return t.card('attachments')
     .get('attachments')
     .then(function(attachments) {
      if(!attachments || !attachments.length) return [];

      return attachments.map(function(attachment){
        var url = new URL(attachment.url);
        //console.log('card-badges', url);
        return {
          icon: 'https://api.faviconkit.com/' + url.hostname + '/32'
        }
      });
    })
  },

  'format-url' : function(t, options) {
    var url = new URL(options.url);
    console.log('format-url', url);
    return {
      icon: 'https://api.faviconkit.com/' + url.hostname + '/32',
      text: options.url
    }
  }
});
<commit_msg>fix: change icon color<commit_after>TrelloPowerUp.initialize({
  'card-badges' : function(t, card) {
    return t.card('attachments')
     .get('attachments')
     .then(function(attachments) {
      return (attachments || []).map(function(attachment){
        var url = new URL(attachment.url);
        return {
          icon: 'https://api.faviconkit.com/' + url.hostname + '/32',
          color: null
        }
      });
    })
  },

  'format-url' : function(t, options) {
    var url = new URL(options.url);
    console.log('format-url', url);
    return {
      icon: 'https://api.faviconkit.com/' + url.hostname + '/32',
      text: options.url
    }
  }
});
<|endoftext|>


<commit_before>const { expect } = require('chai');
const mockery = require('mockery');

const app = require('../../../src/app');
const testObjects = require('../helpers/testObjects')(app);
const { generateRequestParamsFromUser } = require('../helpers/services/login')(app);
const { datasourceRunModel } = require('../../../src/services/datasources/model');
const Syncer = require('../../../src/services/sync/strategies/Syncer');

const datasourcesService = app.service('datasources');
const datasourceRunsService = app.service('datasourceRuns');

/**
 * will always run successfully
 */
class MockSyncer extends Syncer {
	static respondsTo(target) {
		return target === 'mock';
	}

	static params(params, data) {
		return [true];
	}
}

/**
 * will only successfully run if data is provided
 */
class MockSyncerWithData extends Syncer {
	static respondsTo(target) {
		return target === 'mockwithdata';
	}

	static params(params, data) {
		if (data) return [true];
		return false;
	}
}

describe('datasourceRuns service', () => {
	let server;
	before((done) => {
		server = app.listen(0, done);
		mockery.enable({
			useCleanCache: true,
			warnOnUnregistered: false,
		});
		mockery.registerMock('./strategies', [MockSyncer, MockSyncerWithData]);
		// eslint-disable-next-line global-require
		const sync = require('../../../src/services/sync');
		app.configure(sync);
	});

	after((done) => {
		server.close(done);
		mockery.deregisterAll();
		mockery.disable();
	});

	it('registered the datasourceRuns service', () => {
		expect(datasourceRunsService).to.not.be.undefined;
	});

	it('CREATE starts a datasource run without data', async () => {
		const testSchool = await testObjects.createTestSchool();
		const datasource = await testObjects.createTestDatasource({
			schoolId: testSchool._id,
			config: { target: 'mock' },
			name: 'cool datasource',
		});
		const result = await datasourceRunsService.create({ datasourceId: datasource._id });
		expect(result).to.not.equal(undefined);
		expect(result.status).to.equal('Success');
		expect(typeof result.log).to.equal('string');
		expect(result.datasourceId.toString()).to.equal(datasource._id.toString());

		await datasourceRunModel.deleteOne({ _id: result._id }).lean().exec();
	});

	it('CREATE starts a datasource run with data', async () => {
		const testSchool = await testObjects.createTestSchool();
		const datasource = await testObjects.createTestDatasource({
			schoolId: testSchool._id,
			config: { target: 'mockwithdata' },
			name: 'datahungry source',
		});
		const result = await datasourceRunsService.create({ datasourceId: datasource._id, data: 'datakraken-food' });
		expect(result).to.not.equal(undefined);
		expect(result.status).to.equal('Success');
		expect(typeof result.log).to.equal('string');
		expect(result.datasourceId.toString()).to.equal(datasource._id.toString());

		await datasourceRunModel.deleteOne({ _id: result._id }).lean().exec();
	});

	it('CREATE works for an authorized user', async () => {
		const school = await testObjects.createTestSchool();
		const user = await testObjects.createTestUser({ roles: ['administrator'], schoolId: school._id });
		const datasource = await testObjects.createTestDatasource({
			schoolId: school._id,
			config: { target: 'mock' },
			name: 'datahungry source',
		});
		const params = await generateRequestParamsFromUser(user);
		const result = await datasourceRunsService.create(
			{ datasourceId: datasource._id.toString() },
			params,
		);
		expect(result).to.not.equal(undefined);
		expect(result.status).to.equal('Success');
		expect(typeof result.log).to.equal('string');
		expect(result.datasourceId.toString()).to.equal(datasource._id.toString());

		await datasourceRunModel.deleteOne({ _id: result._id }).lean().exec();
	});

	it('CREATE fails for a different school', async () => {
		try {
			const userSchool = await testObjects.createTestSchool();
			const datasourceSchool = await testObjects.createTestSchool();
			const user = await testObjects.createTestUser({ roles: ['administrator'], schoolId: userSchool._id });
			const datasource = await testObjects.createTestDatasource({
				schoolId: datasourceSchool._id,
				config: { target: 'mockwithdata' },
				name: 'datahungry source',
			});
			const params = await generateRequestParamsFromUser(user);
			const result = await datasourceRunsService.create(
				{ datasourceId: datasource._id.toString(), data: 'datakraken-food' },
				params,
			);
			// this should not be reached, but in case it does: clean up
			await datasourceRunModel.deleteOne({ _id: result._id }).lean().exec();
			throw new Error('should have failed');
		} catch (err) {
			expect(err.message).to.not.equal('should have failed');
			expect(err.code).to.equal(403);
			expect(err.className).to.equal('forbidden');
		}
	});

	it('GET fetches a run including log', async () => {
		const testSchool = await testObjects.createTestSchool();
		const datasource = await testObjects.createTestDatasource({
			schoolId: testSchool._id,
			config: { target: 'mock' },
			name: 'awesome datasource',
		});
		const datasourceRun = await datasourceRunsService.create({ datasourceId: datasource._id });
		const result = await datasourceRunsService.get(datasourceRun._id);
		expect(result).to.not.equal(undefined);
		expect(result.status).to.equal('Success');
		expect(typeof result.log).to.equal('string');
		expect(result.datasourceId.toString()).to.equal(datasource._id.toString());

		await datasourceRunModel.deleteOne({ _id: result._id }).lean().exec();
	});

	it('GET fails for a different school', async () => {
		let datasourceRun;
		try {
			const userSchool = await testObjects.createTestSchool();
			const datasourceSchool = await testObjects.createTestSchool();
			const user = await testObjects.createTestUser({ roles: ['administrator'], schoolId: userSchool._id });
			const datasource = await testObjects.createTestDatasource({
				schoolId: datasourceSchool._id,
				config: { target: 'mockwithdata' },
				name: 'datahungry source',
			});
			const params = await generateRequestParamsFromUser(user);
			datasourceRun = await datasourceRunsService
				.create({ datasourceId: datasource._id.toString(), data: 'datakraken-food' });
			await datasourceRunsService.get(datasourceRun._id, params);
			throw new Error('should have failed');
		} catch (err) {
			expect(err.message).to.not.equal('should have failed');
			expect(err.code).to.equal(403);
			expect(err.className).to.equal('forbidden');
		} finally {
			await datasourceRunModel.deleteOne({ _id: datasourceRun._id }).lean().exec();
		}
	});

	it('FIND fetches all runs for a datasource', async () => {
		const testSchool = await testObjects.createTestSchool();
		const datasource = await testObjects.createTestDatasource({
			schoolId: testSchool._id,
			config: { target: 'mock' },
			name: 'beautiful datasource',
		});
		const datasourceRunIds = (await Promise.all([
			datasourceRunsService.create({ datasourceId: datasource._id }),
			datasourceRunsService.create({ datasourceId: datasource._id }),
			datasourceRunsService.create({ datasourceId: datasource._id }),
		])).map((ds) => ds._id.toString());
		const result = await datasourceRunsService.find({ query: { datasourceId: datasource._id } });
		expect(result).to.not.equal(undefined);
		result.data.forEach((res) => {
			expect(res.status).to.equal('Success');
			expect(res.log).to.not.exist;
			expect(datasourceRunIds.includes(res._id.toString())).to.equal(true);
		});
		await datasourceRunModel.remove({ _id: { $in: datasourceRunIds } }).lean().exec();
	});

	it('FIND fetches all runs for a school', async () => {
		const testSchool = await testObjects.createTestSchool();
		const datasource = await testObjects.createTestDatasource({
			schoolId: testSchool._id,
			config: { target: 'mock' },
			name: 'first datasource',
		});
		const otherDatasource = await testObjects.createTestDatasource({
			schoolId: testSchool._id,
			config: { target: 'mock' },
			name: 'second datasource',
		});
		const datasourceRunIds = (await Promise.all([
			datasourceRunsService.create({ datasourceId: datasource._id }),
			datasourceRunsService.create({ datasourceId: otherDatasource._id }),
		])).map((ds) => ds._id.toString());
		const result = await datasourceRunsService.find({ query: { schoolId: testSchool._id } });
		expect(result).to.not.equal(undefined);
		result.data.forEach((res) => {
			expect(res.status).to.equal('Success');
			expect(res.log).to.not.exist;
			expect(datasourceRunIds.includes(res._id.toString())).to.equal(true);
		});
		await datasourceRunModel.remove({ _id: { $in: datasourceRunIds } }).lean().exec();
	});

	it('FIND can be sorted', async () => {
		const testSchool = await testObjects.createTestSchool();
		const datasource = await testObjects.createTestDatasource({
			schoolId: testSchool._id,
			config: { target: 'mock' },
			name: 'first datasource',
		});
		const otherDatasource = await testObjects.createTestDatasource({
			schoolId: testSchool._id,
			config: { target: 'mock' },
			name: 'second datasource',
		});
		const datasourceRunIds = (await Promise.all([
			datasourceRunsService.create({ datasourceId: datasource._id }),
			datasourceRunsService.create({ datasourceId: otherDatasource._id }),
		])).map((ds) => ds._id.toString());
		const ascResult = await datasourceRunsService.find({ query: { schoolId: testSchool._id, sort: '_id' } });
		const descResult = await datasourceRunsService.find({ query: { schoolId: testSchool._id, sort: '-_id' } });
		expect(ascResult).to.not.equal(undefined);
		expect(ascResult.total).to.equal(2);
		expect(descResult).to.not.equal(undefined);
		expect(descResult.total).to.equal(2);
		expect(ascResult.data[0]._id.toString()).to.equal(descResult.data[1]._id.toString());
		expect(ascResult.data[1]._id.toString()).to.equal(descResult.data[0]._id.toString());

		await datasourceRunModel.remove({ _id: { $in: datasourceRunIds } }).lean().exec();
	});

	it('FIND can be paginated', async () => {
		const testSchool = await testObjects.createTestSchool();
		const datasource = await testObjects.createTestDatasource({
			schoolId: testSchool._id,
			config: { target: 'mock' },
			name: 'first datasource',
		});
		const otherDatasource = await testObjects.createTestDatasource({
			schoolId: testSchool._id,
			config: { target: 'mock' },
			name: 'second datasource',
		});
		const datasourceRunIds = (await Promise.all([
			datasourceRunsService.create({ datasourceId: datasource._id }),
			datasourceRunsService.create({ datasourceId: otherDatasource._id }),
		])).map((ds) => ds._id.toString());
		const result = await datasourceRunsService.find({ query: { schoolId: testSchool._id, $skip: 1, $limit: 1 } });
		expect(result).to.not.equal(undefined);
		expect(result.skip).to.equal(1);
		expect(result.limit).to.equal(1);
		expect(Array.isArray(result.data)).to.equal(true);
		expect(result.data.length).to.equal(1);

		await datasourceRunModel.remove({ _id: { $in: datasourceRunIds } }).lean().exec();
	});

	it('FIND doesnt include filtered results', async () => {
		const testSchool = await testObjects.createTestSchool();
		const datasource = await testObjects.createTestDatasource({
			schoolId: testSchool._id,
			config: { target: 'mock' },
			name: 'correct datasource',
		});
		const otherDatasource = await testObjects.createTestDatasource({
			schoolId: testSchool._id,
			config: { target: 'mock' },
			name: 'other datasource',
		});
		const datasourceRunIds = (await Promise.all([
			datasourceRunsService.create({ datasourceId: datasource._id }),
			datasourceRunsService.create({ datasourceId: otherDatasource._id }),
		])).map((ds) => ds._id.toString());
		const result = await datasourceRunsService.find({ query: { datasourceId: datasource._id } });
		expect(result).to.not.equal(undefined);
		expect(result.total).to.equal(1);
		expect(result.data[0]._id.toString()).to.equal(datasourceRunIds[0].toString());

		await datasourceRunModel.remove({ _id: { $in: datasourceRunIds } }).lean().exec();
	});

	it('FIND cant fetch runs from a different school', async () => {
		const testSchool = await testObjects.createTestSchool();
		const otherSchool = await testObjects.createTestSchool();
		const datasource = await testObjects.createTestDatasource({
			schoolId: testSchool._id,
			config: { target: 'mock' },
			name: 'correct datasource',
		});
		const otherDatasource = await testObjects.createTestDatasource({
			schoolId: otherSchool._id,
			config: { target: 'mock' },
			name: 'other datasource',
		});
		const datasourceRunIds = (await Promise.all([
			datasourceRunsService.create({ datasourceId: datasource._id }),
			datasourceRunsService.create({ datasourceId: otherDatasource._id }),
		])).map((ds) => ds._id.toString());
		const user = await testObjects.createTestUser({ roles: ['administrator'], schoolId: testSchool._id });
		const params = await generateRequestParamsFromUser(user);
		const result = await datasourceRunsService.find(params);
		expect(result).to.not.equal(undefined);
		expect(result.total).to.equal(1);
		expect(result.data[0]._id.toString()).to.equal(datasourceRunIds[0].toString());

		await datasourceRunModel.remove({ _id: { $in: datasourceRunIds } }).lean().exec();
	});
});
<commit_msg>remove needless require<commit_after>const { expect } = require('chai');
const mockery = require('mockery');

const app = require('../../../src/app');
const testObjects = require('../helpers/testObjects')(app);
const { generateRequestParamsFromUser } = require('../helpers/services/login')(app);
const { datasourceRunModel } = require('../../../src/services/datasources/model');
const Syncer = require('../../../src/services/sync/strategies/Syncer');

const datasourceRunsService = app.service('datasourceRuns');

/**
 * will always run successfully
 */
class MockSyncer extends Syncer {
	static respondsTo(target) {
		return target === 'mock';
	}

	static params(params, data) {
		return [true];
	}
}

/**
 * will only successfully run if data is provided
 */
class MockSyncerWithData extends Syncer {
	static respondsTo(target) {
		return target === 'mockwithdata';
	}

	static params(params, data) {
		if (data) return [true];
		return false;
	}
}

describe('datasourceRuns service', () => {
	let server;
	before((done) => {
		server = app.listen(0, done);
		mockery.enable({
			useCleanCache: true,
			warnOnUnregistered: false,
		});
		mockery.registerMock('./strategies', [MockSyncer, MockSyncerWithData]);
		// eslint-disable-next-line global-require
		const sync = require('../../../src/services/sync');
		app.configure(sync);
	});

	after((done) => {
		server.close(done);
		mockery.deregisterAll();
		mockery.disable();
	});

	it('registered the datasourceRuns service', () => {
		expect(datasourceRunsService).to.not.be.undefined;
	});

	it('CREATE starts a datasource run without data', async () => {
		const testSchool = await testObjects.createTestSchool();
		const datasource = await testObjects.createTestDatasource({
			schoolId: testSchool._id,
			config: { target: 'mock' },
			name: 'cool datasource',
		});
		const result = await datasourceRunsService.create({ datasourceId: datasource._id });
		expect(result).to.not.equal(undefined);
		expect(result.status).to.equal('Success');
		expect(typeof result.log).to.equal('string');
		expect(result.datasourceId.toString()).to.equal(datasource._id.toString());

		await datasourceRunModel.deleteOne({ _id: result._id }).lean().exec();
	});

	it('CREATE starts a datasource run with data', async () => {
		const testSchool = await testObjects.createTestSchool();
		const datasource = await testObjects.createTestDatasource({
			schoolId: testSchool._id,
			config: { target: 'mockwithdata' },
			name: 'datahungry source',
		});
		const result = await datasourceRunsService.create({ datasourceId: datasource._id, data: 'datakraken-food' });
		expect(result).to.not.equal(undefined);
		expect(result.status).to.equal('Success');
		expect(typeof result.log).to.equal('string');
		expect(result.datasourceId.toString()).to.equal(datasource._id.toString());

		await datasourceRunModel.deleteOne({ _id: result._id }).lean().exec();
	});

	it('CREATE works for an authorized user', async () => {
		const school = await testObjects.createTestSchool();
		const user = await testObjects.createTestUser({ roles: ['administrator'], schoolId: school._id });
		const datasource = await testObjects.createTestDatasource({
			schoolId: school._id,
			config: { target: 'mock' },
			name: 'datahungry source',
		});
		const params = await generateRequestParamsFromUser(user);
		const result = await datasourceRunsService.create(
			{ datasourceId: datasource._id.toString() },
			params,
		);
		expect(result).to.not.equal(undefined);
		expect(result.status).to.equal('Success');
		expect(typeof result.log).to.equal('string');
		expect(result.datasourceId.toString()).to.equal(datasource._id.toString());

		await datasourceRunModel.deleteOne({ _id: result._id }).lean().exec();
	});

	it('CREATE fails for a different school', async () => {
		try {
			const userSchool = await testObjects.createTestSchool();
			const datasourceSchool = await testObjects.createTestSchool();
			const user = await testObjects.createTestUser({ roles: ['administrator'], schoolId: userSchool._id });
			const datasource = await testObjects.createTestDatasource({
				schoolId: datasourceSchool._id,
				config: { target: 'mockwithdata' },
				name: 'datahungry source',
			});
			const params = await generateRequestParamsFromUser(user);
			const result = await datasourceRunsService.create(
				{ datasourceId: datasource._id.toString(), data: 'datakraken-food' },
				params,
			);
			// this should not be reached, but in case it does: clean up
			await datasourceRunModel.deleteOne({ _id: result._id }).lean().exec();
			throw new Error('should have failed');
		} catch (err) {
			expect(err.message).to.not.equal('should have failed');
			expect(err.code).to.equal(403);
			expect(err.className).to.equal('forbidden');
		}
	});

	it('GET fetches a run including log', async () => {
		const testSchool = await testObjects.createTestSchool();
		const datasource = await testObjects.createTestDatasource({
			schoolId: testSchool._id,
			config: { target: 'mock' },
			name: 'awesome datasource',
		});
		const datasourceRun = await datasourceRunsService.create({ datasourceId: datasource._id });
		const result = await datasourceRunsService.get(datasourceRun._id);
		expect(result).to.not.equal(undefined);
		expect(result.status).to.equal('Success');
		expect(typeof result.log).to.equal('string');
		expect(result.datasourceId.toString()).to.equal(datasource._id.toString());

		await datasourceRunModel.deleteOne({ _id: result._id }).lean().exec();
	});

	it('GET fails for a different school', async () => {
		let datasourceRun;
		try {
			const userSchool = await testObjects.createTestSchool();
			const datasourceSchool = await testObjects.createTestSchool();
			const user = await testObjects.createTestUser({ roles: ['administrator'], schoolId: userSchool._id });
			const datasource = await testObjects.createTestDatasource({
				schoolId: datasourceSchool._id,
				config: { target: 'mockwithdata' },
				name: 'datahungry source',
			});
			const params = await generateRequestParamsFromUser(user);
			datasourceRun = await datasourceRunsService
				.create({ datasourceId: datasource._id.toString(), data: 'datakraken-food' });
			await datasourceRunsService.get(datasourceRun._id, params);
			throw new Error('should have failed');
		} catch (err) {
			expect(err.message).to.not.equal('should have failed');
			expect(err.code).to.equal(403);
			expect(err.className).to.equal('forbidden');
		} finally {
			await datasourceRunModel.deleteOne({ _id: datasourceRun._id }).lean().exec();
		}
	});

	it('FIND fetches all runs for a datasource', async () => {
		const testSchool = await testObjects.createTestSchool();
		const datasource = await testObjects.createTestDatasource({
			schoolId: testSchool._id,
			config: { target: 'mock' },
			name: 'beautiful datasource',
		});
		const datasourceRunIds = (await Promise.all([
			datasourceRunsService.create({ datasourceId: datasource._id }),
			datasourceRunsService.create({ datasourceId: datasource._id }),
			datasourceRunsService.create({ datasourceId: datasource._id }),
		])).map((ds) => ds._id.toString());
		const result = await datasourceRunsService.find({ query: { datasourceId: datasource._id } });
		expect(result).to.not.equal(undefined);
		result.data.forEach((res) => {
			expect(res.status).to.equal('Success');
			expect(res.log).to.not.exist;
			expect(datasourceRunIds.includes(res._id.toString())).to.equal(true);
		});
		await datasourceRunModel.remove({ _id: { $in: datasourceRunIds } }).lean().exec();
	});

	it('FIND fetches all runs for a school', async () => {
		const testSchool = await testObjects.createTestSchool();
		const datasource = await testObjects.createTestDatasource({
			schoolId: testSchool._id,
			config: { target: 'mock' },
			name: 'first datasource',
		});
		const otherDatasource = await testObjects.createTestDatasource({
			schoolId: testSchool._id,
			config: { target: 'mock' },
			name: 'second datasource',
		});
		const datasourceRunIds = (await Promise.all([
			datasourceRunsService.create({ datasourceId: datasource._id }),
			datasourceRunsService.create({ datasourceId: otherDatasource._id }),
		])).map((ds) => ds._id.toString());
		const result = await datasourceRunsService.find({ query: { schoolId: testSchool._id } });
		expect(result).to.not.equal(undefined);
		result.data.forEach((res) => {
			expect(res.status).to.equal('Success');
			expect(res.log).to.not.exist;
			expect(datasourceRunIds.includes(res._id.toString())).to.equal(true);
		});
		await datasourceRunModel.remove({ _id: { $in: datasourceRunIds } }).lean().exec();
	});

	it('FIND can be sorted', async () => {
		const testSchool = await testObjects.createTestSchool();
		const datasource = await testObjects.createTestDatasource({
			schoolId: testSchool._id,
			config: { target: 'mock' },
			name: 'first datasource',
		});
		const otherDatasource = await testObjects.createTestDatasource({
			schoolId: testSchool._id,
			config: { target: 'mock' },
			name: 'second datasource',
		});
		const datasourceRunIds = (await Promise.all([
			datasourceRunsService.create({ datasourceId: datasource._id }),
			datasourceRunsService.create({ datasourceId: otherDatasource._id }),
		])).map((ds) => ds._id.toString());
		const ascResult = await datasourceRunsService.find({ query: { schoolId: testSchool._id, sort: '_id' } });
		const descResult = await datasourceRunsService.find({ query: { schoolId: testSchool._id, sort: '-_id' } });
		expect(ascResult).to.not.equal(undefined);
		expect(ascResult.total).to.equal(2);
		expect(descResult).to.not.equal(undefined);
		expect(descResult.total).to.equal(2);
		expect(ascResult.data[0]._id.toString()).to.equal(descResult.data[1]._id.toString());
		expect(ascResult.data[1]._id.toString()).to.equal(descResult.data[0]._id.toString());

		await datasourceRunModel.remove({ _id: { $in: datasourceRunIds } }).lean().exec();
	});

	it('FIND can be paginated', async () => {
		const testSchool = await testObjects.createTestSchool();
		const datasource = await testObjects.createTestDatasource({
			schoolId: testSchool._id,
			config: { target: 'mock' },
			name: 'first datasource',
		});
		const otherDatasource = await testObjects.createTestDatasource({
			schoolId: testSchool._id,
			config: { target: 'mock' },
			name: 'second datasource',
		});
		const datasourceRunIds = (await Promise.all([
			datasourceRunsService.create({ datasourceId: datasource._id }),
			datasourceRunsService.create({ datasourceId: otherDatasource._id }),
		])).map((ds) => ds._id.toString());
		const result = await datasourceRunsService.find({ query: { schoolId: testSchool._id, $skip: 1, $limit: 1 } });
		expect(result).to.not.equal(undefined);
		expect(result.skip).to.equal(1);
		expect(result.limit).to.equal(1);
		expect(Array.isArray(result.data)).to.equal(true);
		expect(result.data.length).to.equal(1);

		await datasourceRunModel.remove({ _id: { $in: datasourceRunIds } }).lean().exec();
	});

	it('FIND doesnt include filtered results', async () => {
		const testSchool = await testObjects.createTestSchool();
		const datasource = await testObjects.createTestDatasource({
			schoolId: testSchool._id,
			config: { target: 'mock' },
			name: 'correct datasource',
		});
		const otherDatasource = await testObjects.createTestDatasource({
			schoolId: testSchool._id,
			config: { target: 'mock' },
			name: 'other datasource',
		});
		const datasourceRunIds = (await Promise.all([
			datasourceRunsService.create({ datasourceId: datasource._id }),
			datasourceRunsService.create({ datasourceId: otherDatasource._id }),
		])).map((ds) => ds._id.toString());
		const result = await datasourceRunsService.find({ query: { datasourceId: datasource._id } });
		expect(result).to.not.equal(undefined);
		expect(result.total).to.equal(1);
		expect(result.data[0]._id.toString()).to.equal(datasourceRunIds[0].toString());

		await datasourceRunModel.remove({ _id: { $in: datasourceRunIds } }).lean().exec();
	});

	it('FIND cant fetch runs from a different school', async () => {
		const testSchool = await testObjects.createTestSchool();
		const otherSchool = await testObjects.createTestSchool();
		const datasource = await testObjects.createTestDatasource({
			schoolId: testSchool._id,
			config: { target: 'mock' },
			name: 'correct datasource',
		});
		const otherDatasource = await testObjects.createTestDatasource({
			schoolId: otherSchool._id,
			config: { target: 'mock' },
			name: 'other datasource',
		});
		const datasourceRunIds = (await Promise.all([
			datasourceRunsService.create({ datasourceId: datasource._id }),
			datasourceRunsService.create({ datasourceId: otherDatasource._id }),
		])).map((ds) => ds._id.toString());
		const user = await testObjects.createTestUser({ roles: ['administrator'], schoolId: testSchool._id });
		const params = await generateRequestParamsFromUser(user);
		const result = await datasourceRunsService.find(params);
		expect(result).to.not.equal(undefined);
		expect(result.total).to.equal(1);
		expect(result.data[0]._id.toString()).to.equal(datasourceRunIds[0].toString());

		await datasourceRunModel.remove({ _id: { $in: datasourceRunIds } }).lean().exec();
	});
});
<|endoftext|>


<commit_before>var buffer = [],
    mark = -1,
    lastChr = "",
    consonents = {
        // key : [character, mahaprana, sanyaka]
        z : [0xda4, 0x00, 0x00],
        Z : [0xda5, 0x00, 0x00],
        w : [0xdca, 0x00, 0x00],
        W : [0x200c, 0x00, 0x00],
        r : [0xdbb, 0x00, 0x00],
        R : [0xdbb, 0x00, 0x00],
        t : [0xdad, 0xdae, 0x00],
        T : [0xda7, 0xda8, 0x00],
        y : [0xdba, 0x00, 0x00],
        Y : [0xdba, 0x00, 0x00],
        p : [0xdb4, 0xdb5, 0x00],
        P : [0xdb5, 0xdb5, 0x00],
        s : [0xdc3, 0xdc2, 0x00],
        S : [0xdc1, 0xdc2, 0x00],
        d : [0xdaf, 0xdb0, 0xdb3],
        D : [0xda9, 0xdaa, 0xdac],
        f : [0xdc6, 0x00, 0x00],
        F : [0xdc6, 0x00, 0x00],
        g : [0xd9c, 0xd9d, 0xd9f],
        G : [0xd9f, 0xd9d, 0x00],
        h : [0xdc4, 0xd83, 0x00],
        H : [0xdc4, 0x00, 0x00],
        j : [0xda2, 0xda3, 0xda6],
        J : [0xda3, 0xda3, 0xda6],
        k : [0xd9a, 0xd9b, 0x00],
        K : [0xd9b, 0xd9b, 0x00],
        l : [0xdbd, 0x00, 0x00],
        L : [0xdc5, 0x00, 0x00],
        x : [0xd82, 0x00, 0x00],
        X : [0xd9e, 0x00, 0x00],
        c : [0xda0, 0xda1, 0x00],
        C : [0xda1, 0xda1, 0x00],
        v : [0xdc0, 0x00, 0x00],
        b : [0xdb6, 0xdb7, 0xdb9],
        B : [0xdb7, 0xdb7, 0xdb9],
        n : [0xdb1, 0x00, 0xd82],
        N : [0xdab, 0x00, 0xd9e],
        m : [0xdb8, 0x00, 0x00],
        M : [0xdb9, 0x00, 0x00]
    },
    vowels = {
        // key : [single0, double0, single1, double1]
        a : [0xd85, 0xd86, 0xdcf, 0xdcf],
        A : [0xd87, 0xd88, 0xdd0, 0xdd1],
        q : [0xd87, 0xd88, 0xdd0, 0xdd1],
        e : [0xd91, 0xd92, 0xdd9, 0xdda],
        E : [0xd91, 0xd92, 0xdd9, 0xdda],
        i : [0xd89, 0xd8a, 0xdd2, 0xdd3],
        I : [0xd93, 0x00, 0xddb, 0xddb],
        o : [0xd94, 0xd95, 0xddc, 0xddd],
        O : [0xd96, 0x00, 0xdde, 0xddf],
        u : [0xd8b, 0xd8c, 0xdd4, 0xdd6],
        U : [0xd8d, 0xd8e, 0xdd8, 0xdf2],
        V : [0xd8f, 0xd90, 0xd8f, 0xd90]
    };

function isAlphabetical(charCode)
{
    if ((charCode >= 65 && charCode <= 90) || (charCode >= 97 && charCode <= 122)) {
        return true
    } else {
        return false
    }
}

function isVowel(charCode)
{
    /* A E I O U V
       a e i o u q */
    var vowelCodes = ["65", "69", "73", "79", "85", "86",
                       "97", "101", "105", "111", "117", "113"];
    for(var i=0;i<vowelCodes.length;i++){
        if(vowelCodes[i]==charCode){return true};
    }
    return false;
}

function isConsonent(charCode)
{
    if (isAlphabetical(charCode) && ! isVowel(charCode)){return true;}
    return false;
}

function transform(e, inputBox)
{
    if (!e) var e = window.event; //for IE

    if (e.which < 65 || e.which > 90) { return; }

    if (!e.charCode && !e.ctrlKey){
        inputBox.value = inputBox.value.slice(0, - 1);
        inputBox.value += lastChr;
    }
}

function sayura(e, inputBox)
{
    if (!e) var e = window.event; //for IE
    if (e.which == 8){
        buffer.pop();
        if (mark != -1) mark--;
        return;
    }

    if (e.ctrlKey) { return; }

    var value = e.charCode;

    if (isAlphabetical(value)) {
        var chr = String.fromCharCode(value);
        buffer.push(value);
        mark++;

        if(isVowel(value)){
            if(buffer.length == 0 || !isAlphabetical(buffer[mark-1])){
                lastChr = String.fromCharCode(vowels[chr][0]);
            } else if(isConsonent(buffer[mark-1])){
                lastChr = String.fromCharCode(vowels[chr][2]);
            } else if(value == buffer[mark-1] && isConsonent(buffer[mark-2])){
                inputBox.value = inputBox.value.slice(0, -1);
                lastChr = String.fromCharCode(vowels[chr][3]);
            } else if(value == buffer[mark-1]){
                inputBox.value = inputBox.value.slice(0, -1);
                lastChr = String.fromCharCode(vowels[chr][1]);
            }

        } else {
            if(value == 71 && isConsonent(buffer[mark-1])){
                //sanyaka
                inputBox.value = inputBox.value.slice(0, -1);
                prevChar = String.fromCharCode(buffer[mark-1]);
                lastChr = String.fromCharCode(consonents[prevChar][2]);
            } else if((value == 72 || value == 102) && isConsonent(buffer[mark-1])){
                inputBox.value = inputBox.value.slice(0, -1);
                //mahaprana
                prevChar = String.fromCharCode(buffer[mark-1]);
                lastChr = String.fromCharCode(consonents[prevChar][1]);
            } else if((value == 82 || value == 89) && isConsonent(buffer[mark-1])){
                //add al-kirima, zero width joiner and r/y for rakaransaya/yansaya
                lastChr = String.fromCharCode(0xdca, 0x200d, consonents[chr][0]);
            } else if(value == 87 && isConsonent(buffer[mark-1])){
                //zero width joiner, al-kirima for bandi-akuru
                lastChr = String.fromCharCode(0x200d, 0xdca);
            } else {
                lastChr = String.fromCharCode(consonents[chr][0]);
            }
        }

    } else if (e.charCode != 0){
        lastChr = String.fromCharCode(value);
        buffer.push(String.fromCharCode(value));
        mark++;
    }
}

function load()
{
    if (document.getElementById("sayuraInput")){
        input = document.getElementById("sayuraInput")
    }else{
        var input = document.activeElement;
    }

    input.addEventListener("keypress", function(e){sayura(e, input)}, false);
    input.addEventListener("keyup", function(e){transform(e, input)}, false);
}

document.addEventListener("click", load, false);
<commit_msg>Fixes a minor issue<commit_after>var buffer = [],
    mark = -1,
    lastChr = "",
    consonents = {
        // key : [character, mahaprana, sanyaka]
        z : [0xda4, 0x00, 0x00],
        Z : [0xda5, 0x00, 0x00],
        w : [0xdca, 0x00, 0x00],
        W : [0x200c, 0x00, 0x00],
        r : [0xdbb, 0x00, 0x00],
        R : [0xdbb, 0x00, 0x00],
        t : [0xdad, 0xdae, 0x00],
        T : [0xda7, 0xda8, 0x00],
        y : [0xdba, 0x00, 0x00],
        Y : [0xdba, 0x00, 0x00],
        p : [0xdb4, 0xdb5, 0x00],
        P : [0xdb5, 0xdb5, 0x00],
        s : [0xdc3, 0xdc2, 0x00],
        S : [0xdc1, 0xdc2, 0x00],
        d : [0xdaf, 0xdb0, 0xdb3],
        D : [0xda9, 0xdaa, 0xdac],
        f : [0xdc6, 0x00, 0x00],
        F : [0xdc6, 0x00, 0x00],
        g : [0xd9c, 0xd9d, 0xd9f],
        G : [0xd9f, 0xd9d, 0x00],
        h : [0xdc4, 0xd83, 0x00],
        H : [0xdc4, 0x00, 0x00],
        j : [0xda2, 0xda3, 0xda6],
        J : [0xda3, 0xda3, 0xda6],
        k : [0xd9a, 0xd9b, 0x00],
        K : [0xd9b, 0xd9b, 0x00],
        l : [0xdbd, 0x00, 0x00],
        L : [0xdc5, 0x00, 0x00],
        x : [0xd82, 0x00, 0x00],
        X : [0xd9e, 0x00, 0x00],
        c : [0xda0, 0xda1, 0x00],
        C : [0xda1, 0xda1, 0x00],
        v : [0xdc0, 0x00, 0x00],
        b : [0xdb6, 0xdb7, 0xdb9],
        B : [0xdb7, 0xdb7, 0xdb9],
        n : [0xdb1, 0x00, 0xd82],
        N : [0xdab, 0x00, 0xd9e],
        m : [0xdb8, 0x00, 0x00],
        M : [0xdb9, 0x00, 0x00]
    },
    vowels = {
        // key : [single0, double0, single1, double1]
        a : [0xd85, 0xd86, 0xdcf, 0xdcf],
        A : [0xd87, 0xd88, 0xdd0, 0xdd1],
        q : [0xd87, 0xd88, 0xdd0, 0xdd1],
        e : [0xd91, 0xd92, 0xdd9, 0xdda],
        E : [0xd91, 0xd92, 0xdd9, 0xdda],
        i : [0xd89, 0xd8a, 0xdd2, 0xdd3],
        I : [0xd93, 0x00, 0xddb, 0xddb],
        o : [0xd94, 0xd95, 0xddc, 0xddd],
        O : [0xd96, 0x00, 0xdde, 0xddf],
        u : [0xd8b, 0xd8c, 0xdd4, 0xdd6],
        U : [0xd8d, 0xd8e, 0xdd8, 0xdf2],
        V : [0xd8f, 0xd90, 0xd8f, 0xd90]
    };

function isAlphabetical(charCode)
{
    if ((charCode >= 65 && charCode <= 90) || (charCode >= 97 && charCode <= 122)) {
        return true
    } else {
        return false
    }
}

function isVowel(charCode)
{
    /* A E I O U V
       a e i o u q */
    var vowelCodes = ["65", "69", "73", "79", "85", "86",
                       "97", "101", "105", "111", "117", "113"];
    for(var i=0;i<vowelCodes.length;i++){
        if(vowelCodes[i]==charCode){return true};
    }
    return false;
}

function isConsonent(charCode)
{
    if (isAlphabetical(charCode) && ! isVowel(charCode)){return true;}
    return false;
}

function transform(e, inputBox)
{
    if (!e) var e = window.event; //for IE

    if (e.which < 65 || e.which > 90) { return; }

    if (!e.charCode && !e.ctrlKey){
        inputBox.value = inputBox.value.slice(0, - 1);
        inputBox.value += lastChr;
    }
}

function sayura(e, inputBox)
{
    if (!e) var e = window.event; //for IE
    if (e.which == 8){
        buffer.pop();
        if (mark != -1) mark--;
        return;
    }

    if (e.ctrlKey) { return; }

    var value = e.charCode;

    if (isAlphabetical(value)) {
        var chr = String.fromCharCode(value);
        buffer.push(value);
        mark++;

        if(isVowel(value)){
            if(buffer.length == 0 || !isAlphabetical(buffer[mark-1])){
                lastChr = String.fromCharCode(vowels[chr][0]);
            } else if(isConsonent(buffer[mark-1])){
                lastChr = String.fromCharCode(vowels[chr][2]);
            } else if(value == buffer[mark-1] && isConsonent(buffer[mark-2])){
                inputBox.value = inputBox.value.slice(0, -1);
                lastChr = String.fromCharCode(vowels[chr][3]);
            } else if(value == buffer[mark-1]){
                inputBox.value = inputBox.value.slice(0, -1);
                lastChr = String.fromCharCode(vowels[chr][1]);
            }

        } else {
            if(value == 71 && isConsonent(buffer[mark-1])){
                //sanyaka
                inputBox.value = inputBox.value.slice(0, -1);
                prevChar = String.fromCharCode(buffer[mark-1]);
                lastChr = String.fromCharCode(consonents[prevChar][2]);
            } else if((value == 72 || value == 102) && isConsonent(buffer[mark-1])){
                inputBox.value = inputBox.value.slice(0, -1);
                //mahaprana
                prevChar = String.fromCharCode(buffer[mark-1]);
                lastChr = String.fromCharCode(consonents[prevChar][1]);
            } else if((value == 82 || value == 89) && isConsonent(buffer[mark-1])){
                //add al-kirima, zero width joiner and r/y for rakaransaya/yansaya
                lastChr = String.fromCharCode(0xdca, 0x200d, consonents[chr][0]);
            } else if(value == 87 && isConsonent(buffer[mark-1])){
                //zero width joiner, al-kirima for bandi-akuru
                lastChr = String.fromCharCode(0x200d, 0xdca);
            } else {
                lastChr = String.fromCharCode(consonents[chr][0]);
            }
        }

    } else if (e.charCode != 0){
        lastChr = String.fromCharCode(value);
        buffer.push(String.fromCharCode(value));
        mark++;
    }
}

function load()
{
    var input;
    if (document.getElementById("sayuraInput")){
        input = document.getElementById("sayuraInput");
    }else{
        input = document.activeElement;
    }

    input.addEventListener("keypress", function(e){sayura(e, input)}, false);
    input.addEventListener("keyup", function(e){transform(e, input)}, false);
}

document.addEventListener("click", load, false);
<|endoftext|>


<commit_before>import { React, PropTypes, observer, cn } from '../helpers/react';
import { observable, action, computed, when } from 'mobx';
import { autobind } from 'core-decorators';
import { Icon, Logging } from 'shared';
import User from '../models/user';
import { filter, last, sortBy } from 'lodash';
import SummaryPage from './notes/summary-page';
import dom from '../helpers/dom';
import imagesComplete from '../helpers/images-complete';
import Courses from '../models/courses-map';
import EditBox from './notes/edit-box';
import SidebarButtons from './notes/sidebar-buttons';
import InlineControls from './notes/inline-controls';
import ScrollTo from '../helpers/scroll-to';
import Highlighter from '@openstax/highlighter';
import Router from '../helpers/router';
import NotesMap from '../models/notes';
import Overlay from './obscured-page/overlay';

export default
@observer
class NotesWidget extends React.Component {

  static propTypes = {
    courseId: PropTypes.string.isRequired,
    documentId: PropTypes.string,
    windowImpl: PropTypes.shape({
      open: PropTypes.func,
    }),
    title: PropTypes.string,
    chapter: PropTypes.number.isRequired,
    section: PropTypes.number.isRequired,
    notes: PropTypes.instanceOf(NotesMap),
  };

  static defaultProps = {
    notes: User.notes,
    windowImpl: window,
  };

  scrollHelper = new ScrollTo({ windowImpl: this.props.windowImpl, scrollingTargetClass: false });
  highlightScrollHandler = elements => this.scrollHelper.scrollToElement(last(elements), {
    scrollTopOffset: (window.innerHeight / 2) - 80,
  });

  @observable scrollToPendingNote;
  @observable highlighter;
  @observable referenceElements = [];
  @observable activeNote;
  @observable pendingHighlight;

  componentDidMount() {
    if (!this.course.canAnnotate) { return; }

    const { highlight } = Router.currentQuery();

    if (highlight) {
      this.setupPendingHighlightScroll(highlight);
    }

    when(
      () => !this.props.notes.api.isPending,
      () => this.initializePage(),
    );
  }

  componentWillReceiveProps() {
    if (!this.course.canAnnotate) { return; }
    this.activeNote = null;
    this.initializePage();
  }

  componentWillUnmount() {
    if (this.highlighter) {
      this.highlighter.unmount();
    }
  }

  @computed get course() {
    return Courses.get(this.props.courseId);
  }

  @computed get notesForThisPage() {
    return this.allNotesForThisBook.filter(item =>
      (item.chapter === this.props.chapter) &&
      (item.section === this.props.section) &&
      this.highlighter &&
      item.highlight.isLoadable(this.highlighter)
    );
  }

  @computed get allNotesForThisBook() {
    return filter(this.props.notes.array, { courseId: this.props.courseId });
  }

  setupPendingHighlightScroll(highlightId) {
    this.scrollToPendingNote = () => {
      const highlight = this.highlighter.getHighlight(highlightId);

      this.highlighter.clearFocus();
      if (highlight) {
        highlight.focus().scrollTo(this.highlightScrollHandler);
      } else {
        Logging.error(`Page attempted to scroll to note id '${highlightId}' but it was not found`);
      }
      this.scrollToPendingNote = null;
    };
  }

  waitForPageReady() {
    return new Promise(resolve => {
      const win = this.props.windowImpl;
      const unprocessedMath = !!win.document.querySelector('.book-content *:not(.MJX_Assistive_MathML) > math');
      const runImagesComplete = () => imagesComplete({
        body: win.document.querySelector('.book-content'),
      })
        .finally(resolve)
      ;

      if (win.MathJax && unprocessedMath) {
        win.MathJax.Hub.Register.MessageHook('End Process', runImagesComplete);
      } else {
        runImagesComplete();
      }
    });
  }

  getBookContentRef() {
    return this.props.windowImpl.document.querySelector('.book-content');
  }

  initializePage() {
    this.ux.statusMessage.show({
      type: 'info',
      message: 'Waiting for page to finish loading…',
    });

    this.getReferenceElements();
    if (!this.referenceElements.length) { return Promise.resolve(); }

    const initialize = action(() => {
      // remove any existing highlighter
      if (this.highlighter) {
        this.highlighter.unmount();
      }
      // create a new highlighter
      this.highlighter = new Highlighter(this.getBookContentRef(), {
        snapTableRows: true,
        snapMathJax: true,
        snapWords: true,
        className: 'tutor-highlight',
        onClick: this.onHighlightClick,
        onSelect: this.onHighlightSelect,
      });
      // attach notes to highlghter
      this.notesForThisPage.forEach(note => this.highlighter.highlight(note.highlight));
      // scroll if needed
      if (this.scrollToPendingNote) {
        this.scrollToPendingNote();
      }
      // and we're done
      this.ux.statusMessage.hide();
    });

    return this.waitForPageReady().then(initialize);
  }

  @action.bound onHighlightClick(highlight) {
    const note = highlight ? this.props.notes.get(highlight.id) : null;
    this.pendingHighlight = null;
    this.activeNote = note;

    this.highlighter.clearFocus();
    if (highlight) {
      highlight.focus().scrollTo(this.highlightScrollHandler);
    }
  }

  cantHighlightReason(highlights, highlight) {
    if (highlights.length > 0) {
      return 'Highlights cannot overlap one another';
    }
    const node = dom(highlight.range.commonAncestorContainer);

    if (!node.closest('.book-content')) {
      return 'Only content can be highlighted';
    }

    for (const re of this.referenceElements) {
      if (dom(re).isParent(node.el)) {
        return null;
      }
    }

    return 'Only content that is enclosed in paragraphs can be highlighted';
  }

  @action.bound onHighlightSelect(highlights, highlight) {
    this.activeNote = null;
    const error = this.cantHighlightReason(highlights, highlight);

    if (error) {
      this.pendingHighlight = null;
      this.ux.statusMessage.show({ message: error, autoHide: true });
    } else {
      this.pendingHighlight = highlight;
      this.ux.statusMessage.hide();
    }
  }

  @action
  getReferenceElements() {
    this.referenceElements = Array.from(
      this.props.windowImpl.document.querySelectorAll('.book-content > [id]')
    ).reverse();
  }

  @autobind
  highlightAndClose() {
    return this.saveNewHighlight().then(
      action(note => {
        this.props.windowImpl.getSelection().removeAllRanges();
        this.pendingHighlight = null;
        this.highlighter.highlight(note.highlight);
        return note;
      }));
  }

  @autobind
  openAnnotator() {
    return this.highlightAndClose().then(note => this.activeNote = note);
  }

  @autobind
  saveNewHighlight() {
    const highlight = this.pendingHighlight;

    const referenceElement = this.referenceElements
      .find(re => dom(re).isParent(highlight.range.commonAncestorContainer));

    const serializedHighlight = highlight.serialize(referenceElement);

    return this.props.notes.create({
      research_identifier: this.course.primaryRole.research_identifier,
      userRole: this.course.primaryRole.type,
      documentId: this.props.documentId,
      courseId: this.props.courseId,
      chapter: this.props.chapter,
      section: this.props.section,
      title: this.props.title,
      rect: dom(highlight.range).boundingClientRect,
      ...serializedHighlight.data,
    });
  }

  @computed get sortedNotesForPage() {
    return sortBy(
      this.notesForThisPage,
      ['selection.bounds.top', 'selection.start']
    );
  }

  getNoteByOffset(offset) {
    const note = this.activeNote;
    if (!note) {
      return null;
    }
    const highlight = this.highlighter.getHighlight(note.id);
    if (!highlight) {
      return null;
    }

    const highlights = this.highlighter.getHighlights();
    const targetIndex = highlights.indexOf(highlight) + offset;

    if (!highlights[targetIndex]) {
      return null;
    }

    const targetNoteId = highlights[targetIndex].id;

    return this.props.notes.get(targetNoteId);
  }

  get nextNote() {
    return this.getNoteByOffset(1);
  }

  get previousNote() {
    return this.getNoteByOffset(-1);
  }

  @action.bound setElement(el) {
    this.element = el;
  }

  get parentRect() {
    if (!this.element) {
      return { top: 0, bottom: 0, left: 0, right: 0 };
    }
    const wLeft = this.props.windowImpl.pageXOffset;
    const wTop = this.props.windowImpl.pageYOffset;
    const parentRect =  this.element.parentElement.getBoundingClientRect();
    return {
      bottom: wTop + parentRect.bottom,
      left: wLeft + parentRect.left,
      right: wLeft + parentRect.right,
      top: wTop + parentRect.top,
    };
  }

  @computed get ux() { return this.props.notes.ux; }

  @action.bound seeAll() {
    this.ux.isSummaryVisible = true;
    this.activeNote = null;
  }

  @action.bound editNote(note) {
    this.activeNote = note;

    this.highlighter.clearFocus();
    const highlight = this.highlighter.getHighlight(note.id);
    if (highlight) {
      highlight.focus().scrollTo(this.highlightScrollHandler);
    }
  }

  @action.bound hideActiveHighlight() {
    this.activeNote = null;
  }

  @action.bound onNoteDelete(note) {
    const highlight = this.highlighter.getHighlight(note.id);
    if (highlight) {
      this.highlighter.erase(highlight);
    }
  }

  renderStatusMessage() {
    if (!this.ux.statusMessage.display) { return null; }

    return (
      <div
        className={cn('status-message-toast', this.ux.statusMessage.type)}
      >
        <Icon type={this.ux.statusMessage.icon} /> {this.ux.statusMessage.message}
      </div>
    );
  }

  render() {
    if (!this.course.canAnnotate) { return null; }

    return (
      <div className="annotater" ref={this.setElement}>
        <InlineControls
          pendingHighlight={this.pendingHighlight}
          windowImpl={this.props.windowImpl}
          parentRect={this.parentRect}
          annotate={this.openAnnotator}
          highlight={this.highlightAndClose}
        />
        <EditBox
          note={this.activeNote}
          onHide={this.hideActiveHighlight}
          onDelete={this.onNoteDelete}
          goToNote={this.editNote}
          next={this.nextNote}
          previous={this.previousNote}
          seeAll={this.seeAll}
        />
        <SidebarButtons
          windowImpl={this.props.windowImpl}
          highlighter={this.highlighter}
          notes={this.notesForThisPage}
          parentRect={this.parentRect}
          onClick={this.editNote}
          activeNote={this.activeNote}
        />
        {this.renderStatusMessage()}
        <Overlay
          id="notes-summary"
          visible={this.ux.isSummaryVisible}
          onHide={this.ux.hideSummary}
          renderer={() => (
            <SummaryPage
              courseId={this.props.courseId}
              notes={this.props.notes}
              onDelete={this.onNoteDelete}
              currentChapter={this.props.chapter}
              currentSection={this.props.section}
            />
          )}
        />
      </div>
    );
  }

};
<commit_msg>debounce initialize and don't cache reference elements<commit_after>import { React, PropTypes, observer, cn } from '../helpers/react';
import { observable, action, computed, when } from 'mobx';
import { autobind } from 'core-decorators';
import { Icon, Logging } from 'shared';
import User from '../models/user';
import { filter, last, sortBy, debounce } from 'lodash';
import SummaryPage from './notes/summary-page';
import dom from '../helpers/dom';
import imagesComplete from '../helpers/images-complete';
import Courses from '../models/courses-map';
import EditBox from './notes/edit-box';
import SidebarButtons from './notes/sidebar-buttons';
import InlineControls from './notes/inline-controls';
import ScrollTo from '../helpers/scroll-to';
import Highlighter from '@openstax/highlighter';
import Router from '../helpers/router';
import NotesMap from '../models/notes';
import Overlay from './obscured-page/overlay';

export default
@observer
class NotesWidget extends React.Component {

  static propTypes = {
    courseId: PropTypes.string.isRequired,
    documentId: PropTypes.string,
    windowImpl: PropTypes.shape({
      open: PropTypes.func,
    }),
    title: PropTypes.string,
    chapter: PropTypes.number.isRequired,
    section: PropTypes.number.isRequired,
    notes: PropTypes.instanceOf(NotesMap),
  };

  static defaultProps = {
    notes: User.notes,
    windowImpl: window,
  };

  scrollHelper = new ScrollTo({ windowImpl: this.props.windowImpl, scrollingTargetClass: false });
  highlightScrollHandler = elements => this.scrollHelper.scrollToElement(last(elements), {
    scrollTopOffset: (window.innerHeight / 2) - 80,
  });

  @observable scrollToPendingNote;
  @observable highlighter;
  @observable activeNote;
  @observable pendingHighlight;

  componentDidMount() {
    if (!this.course.canAnnotate) { return; }

    const { highlight } = Router.currentQuery();

    if (highlight) {
      this.setupPendingHighlightScroll(highlight);
    }

    when(
      () => !this.props.notes.api.isPending,
      () => this.initializePage(),
    );
  }

  componentWillReceiveProps(nextProps) {
    if (!this.course.canAnnotate) { return; }
    this.activeNote = null;
    this.initializePage();
  }

  componentWillUnmount() {
    if (this.highlighter) {
      this.highlighter.unmount();
    }
  }

  @computed get course() {
    return Courses.get(this.props.courseId);
  }

  @computed get notesForThisPage() {
    return this.allNotesForThisBook.filter(item =>
      (item.chapter === this.props.chapter) &&
      (item.section === this.props.section) &&
      this.highlighter &&
      item.highlight.isLoadable(this.highlighter)
    );
  }

  @computed get allNotesForThisBook() {
    return filter(this.props.notes.array, { courseId: this.props.courseId });
  }

  setupPendingHighlightScroll(highlightId) {
    this.scrollToPendingNote = () => {
      const highlight = this.highlighter.getHighlight(highlightId);

      this.highlighter.clearFocus();
      if (highlight) {
        highlight.focus().scrollTo(this.highlightScrollHandler);
      } else {
        Logging.error(`Page attempted to scroll to note id '${highlightId}' but it was not found`);
      }
      this.scrollToPendingNote = null;
    };
  }

  waitForPageReady() {
    return new Promise(resolve => {
      const win = this.props.windowImpl;
      const unprocessedMath = !!win.document.querySelector('.book-content *:not(.MJX_Assistive_MathML) > math');
      const runImagesComplete = () => imagesComplete({
        body: win.document.querySelector('.book-content'),
      })
        .finally(resolve)
      ;
      if (win.MathJax && unprocessedMath) {
        win.MathJax.Hub.Register.MessageHook('End Process', runImagesComplete);
      } else {
        runImagesComplete();
      }
    });
  }

  getBookContentRef() {
    return this.props.windowImpl.document.querySelector('.book-content');
  }

  initializePage = debounce(() => {
    this.ux.statusMessage.show({
      type: 'info',
      message: 'Waiting for page to finish loading…',
    });

    const initialize = action(() => {
      if (this.highlighter) {
        this.highlighter.unmount();
      }
      // create a new highlighter
      this.highlighter = new Highlighter(this.getBookContentRef(), {
        snapTableRows: true,
        snapMathJax: true,
        snapWords: true,
        className: 'tutor-highlight',
        onClick: this.onHighlightClick,
        onSelect: this.onHighlightSelect,
      });
      // attach notes to highlghter
      this.notesForThisPage.forEach(note => this.highlighter.highlight(note.highlight));
      // scroll if needed
      if (this.scrollToPendingNote) {
        this.scrollToPendingNote();
      }
      // and we're done
      this.ux.statusMessage.hide();
    });

    this.waitForPageReady().then(initialize);
  }, 100)

  @action.bound onHighlightClick(highlight) {
    const note = highlight ? this.props.notes.get(highlight.id) : null;
    this.pendingHighlight = null;
    this.activeNote = note;

    this.highlighter.clearFocus();
    if (highlight) {
      highlight.focus().scrollTo(this.highlightScrollHandler);
    }
  }

  cantHighlightReason(highlights, highlight) {
    if (highlights.length > 0) {
      return 'Highlights cannot overlap one another';
    }
    const node = dom(highlight.range.commonAncestorContainer);

    if (!node.closest('.book-content')) {
      return 'Only content can be highlighted';
    }

    for (const re of this.referenceElements) {
      if (dom(re).isParent(node.el)) {
        return null;
      }
    }

    return 'Only content that is enclosed in paragraphs can be highlighted';
  }

  @action.bound onHighlightSelect(highlights, highlight) {
    this.activeNote = null;
    const error = this.cantHighlightReason(highlights, highlight);

    if (error) {
      this.pendingHighlight = null;
      this.ux.statusMessage.show({ message: error, autoHide: true });
    } else {
      this.pendingHighlight = highlight;
      this.ux.statusMessage.hide();
    }
  }

  get documentRoot() {
    const doc = this.props.windowImpl.document;
    return doc.querySelector('[data-type="composite-page"]') || doc.querySelector('.book-content') || doc;

  }

  get referenceElements() {
    return Array.from(this.documentRoot.children)
      .filter(e => e.matches('[id]'))
      .reverse();
  }

  @autobind
  highlightAndClose() {
    return this.saveNewHighlight().then(
      action(note => {
        this.props.windowImpl.getSelection().removeAllRanges();
        this.pendingHighlight = null;
        this.highlighter.highlight(note.highlight);
        return note;
      }));
  }

  @autobind
  openAnnotator() {
    return this.highlightAndClose().then(note => this.activeNote = note);
  }

  @autobind
  saveNewHighlight() {
    const highlight = this.pendingHighlight;

    const referenceElement = this.referenceElements
      .find(re => dom(re).isParent(highlight.range.commonAncestorContainer));

    const serializedHighlight = highlight.serialize(referenceElement);

    return this.props.notes.create({
      research_identifier: this.course.primaryRole.research_identifier,
      userRole: this.course.primaryRole.type,
      documentId: this.props.documentId,
      courseId: this.props.courseId,
      chapter: this.props.chapter,
      section: this.props.section,
      title: this.props.title,
      rect: dom(highlight.range).boundingClientRect,
      ...serializedHighlight.data,
    });
  }

  @computed get sortedNotesForPage() {
    return sortBy(
      this.notesForThisPage,
      ['selection.bounds.top', 'selection.start']
    );
  }

  getNoteByOffset(offset) {
    const note = this.activeNote;
    if (!note) {
      return null;
    }
    const highlight = this.highlighter.getHighlight(note.id);
    if (!highlight) {
      return null;
    }

    const highlights = this.highlighter.getHighlights();
    const targetIndex = highlights.indexOf(highlight) + offset;

    if (!highlights[targetIndex]) {
      return null;
    }

    const targetNoteId = highlights[targetIndex].id;

    return this.props.notes.get(targetNoteId);
  }

  get nextNote() {
    return this.getNoteByOffset(1);
  }

  get previousNote() {
    return this.getNoteByOffset(-1);
  }

  @action.bound setElement(el) {
    this.element = el;
  }

  get parentRect() {
    if (!this.element) {
      return { top: 0, bottom: 0, left: 0, right: 0 };
    }
    const wLeft = this.props.windowImpl.pageXOffset;
    const wTop = this.props.windowImpl.pageYOffset;
    const parentRect =  this.element.parentElement.getBoundingClientRect();
    return {
      bottom: wTop + parentRect.bottom,
      left: wLeft + parentRect.left,
      right: wLeft + parentRect.right,
      top: wTop + parentRect.top,
    };
  }

  @computed get ux() { return this.props.notes.ux; }

  @action.bound seeAll() {
    this.ux.isSummaryVisible = true;
    this.activeNote = null;
  }

  @action.bound editNote(note) {
    this.activeNote = note;

    this.highlighter.clearFocus();
    const highlight = this.highlighter.getHighlight(note.id);
    if (highlight) {
      highlight.focus().scrollTo(this.highlightScrollHandler);
    }
  }

  @action.bound hideActiveHighlight() {
    this.activeNote = null;
  }

  @action.bound onNoteDelete(note) {
    const highlight = this.highlighter.getHighlight(note.id);
    if (highlight) {
      this.highlighter.erase(highlight);
    }
  }

  renderStatusMessage() {
    if (!this.ux.statusMessage.display) { return null; }

    return (
      <div
        className={cn('status-message-toast', this.ux.statusMessage.type)}
      >
        <Icon type={this.ux.statusMessage.icon} /> {this.ux.statusMessage.message}
      </div>
    );
  }

  render() {
    if (!this.course.canAnnotate) { return null; }

    return (
      <div className="annotater" ref={this.setElement}>
        <InlineControls
          pendingHighlight={this.pendingHighlight}
          windowImpl={this.props.windowImpl}
          parentRect={this.parentRect}
          annotate={this.openAnnotator}
          highlight={this.highlightAndClose}
        />
        <EditBox
          note={this.activeNote}
          onHide={this.hideActiveHighlight}
          onDelete={this.onNoteDelete}
          goToNote={this.editNote}
          next={this.nextNote}
          previous={this.previousNote}
          seeAll={this.seeAll}
        />
        <SidebarButtons
          windowImpl={this.props.windowImpl}
          highlighter={this.highlighter}
          notes={this.notesForThisPage}
          parentRect={this.parentRect}
          onClick={this.editNote}
          activeNote={this.activeNote}
        />
        {this.renderStatusMessage()}
        <Overlay
          id="notes-summary"
          visible={this.ux.isSummaryVisible}
          onHide={this.ux.hideSummary}
          renderer={() => (
            <SummaryPage
              courseId={this.props.courseId}
              notes={this.props.notes}
              onDelete={this.onNoteDelete}
              currentChapter={this.props.chapter}
              currentSection={this.props.section}
            />
          )}
        />
      </div>
    );
  }

};
<|endoftext|>


<commit_before>(function(){

   if (typeof JSROOT != "object") {
      var e1 = new Error("httptextlog.js requires JSROOT to be already loaded");
      e1.source = "httptextlog.js";
      throw e1;
   }

   function MakeMsgListRequest(hitem, item) {
      // this function produces url for http request
      // here one provides id of last string received with previous request

      var arg = "&max=1000";
      if ('last-id' in item) arg+= "&id="+item['last-id'];
      return 'exe.json.gz?method=Select' + arg;
   }

   function AfterMsgListRequest(hitem, item, obj) {
      // after data received, one replaces typename for produced object

      if (item==null) return;

      if (obj==null) {
         delete item['last-id'];
         return;
      }
      // ignore all other classes
      if (obj['_typename'] != 'TList') return;

      // change class name - it is only important for drawing
      obj['_typename'] = "TMsgList";

      if (obj.arr.length>0) {
         item['last-id'] = obj.arr[0].fString;

         // add clear function for item
         if (!('clear' in item))
            item['clear'] = function() { delete this['last-id']; }
      }
   }

   function DrawMsgList(divid, lst, opt) {

      var painter = new JSROOT.TBasePainter();
      painter.SetDivId(divid);

      painter.Draw = function(lst) {
         if (lst == null) return;

         var frame = d3.select("#" + this.divid);

         var main = frame.select("div");
         if (main.empty())
            main = frame.append("div")
                        .style('max-width','100%')
                        .style('max-height','100%')
                        .style('overflow','auto');

         var old = main.selectAll("pre");
         var newsize = old.size() + lst.arr.length - 1;

         // in the browser keep maximum 1000 entries
         if (newsize > 1000)
            old.select(function(d,i) { return i < newsize - 1000 ? this : null; }).remove();

         for (var i=lst.arr.length-1;i>0;i--)
            main.append("pre").html(lst.arr[i].fString);

         // (re) set painter to first child element
         this.SetDivId(this.divid);
      }

      painter.RedrawObject = function(obj) {
         this.Draw(obj);
         return true;
      }

      painter.Draw(lst);
      return painter.DrawingReady();
   }

   JSROOT.addDrawFunc({name:"TMsgList", icon:"img_text", make_request:MakeMsgListRequest, after_request:AfterMsgListRequest, func:DrawMsgList, opt:"list"});

})();
<commit_msg>http: update tutorial, style adjustment for JSROOT 5.1.0<commit_after>/// \file
/// \ingroup tutorial_http
///
/// \macro_code
///
/// \author

(function(){

   if (typeof JSROOT != "object") {
      var e1 = new Error("httptextlog.js requires JSROOT to be already loaded");
      e1.source = "httptextlog.js";
      throw e1;
   }

   function MakeMsgListRequest(hitem, item) {
      // this function produces url for http request
      // here one provides id of last string received with previous request

      var arg = "&max=1000";
      if ('last-id' in item) arg+= "&id="+item['last-id'];
      return 'exe.json.gz?method=Select' + arg;
   }

   function AfterMsgListRequest(hitem, item, obj) {
      // after data received, one replaces typename for produced object

      if (item==null) return;

      if (obj==null) {
         delete item['last-id'];
         return;
      }
      // ignore all other classes
      if (obj._typename != 'TList') return;

      // change class name - it is only important for drawing
      obj._typename = "TMsgList";

      if (obj.arr.length>0) {
         item['last-id'] = obj.arr[0].fString;

         // add clear function for item
         if (!('clear' in item))
            item['clear'] = function() { delete this['last-id']; }
      }
   }

   function DrawMsgList(divid, lst, opt) {

      var painter = new JSROOT.TBasePainter();
      painter.SetDivId(divid);

      painter.Draw = function(lst) {
         if (lst == null) return;

         var frame = d3.select("#" + this.divid);

         var main = frame.select("div");
         if (main.empty())
            main = frame.append("div")
                        .style('max-width','100%')
                        .style('max-height','100%')
                        .style('overflow','auto');

         var old = main.selectAll("pre");
         var newsize = old.size() + lst.arr.length - 1;

         // in the browser keep maximum 1000 entries
         if (newsize > 1000)
            old.select(function(d,i) { return i < newsize - 1000 ? this : null; }).remove();

         for (var i=lst.arr.length-1;i>0;i--)
            main.append("pre").style('margin','2px').html(lst.arr[i].fString);

         // (re) set painter to first child element
         this.SetDivId(this.divid);
      }

      painter.RedrawObject = function(obj) {
         this.Draw(obj);
         return true;
      }

      painter.Draw(lst);
      return painter.DrawingReady();
   }

   JSROOT.addDrawFunc({name:"TMsgList", icon:"img_text", make_request:MakeMsgListRequest, after_request:AfterMsgListRequest, func:DrawMsgList, opt:"list"});

})();
<|endoftext|>


<commit_before>module.exports = {
  entry: {
    bundle1: './entry1.js',
    bundle2: './entry2.js'
  },
  output: {
    filename: '[name].js'
  }
};
<commit_msg>webpack multiple entry<commit_after>module.exports = {
//  多个文档，单独打包
  // entry: {
  //   bundle1: './entry1.js',
  //   bundle2: './entry2.js'
  // },
  // output: {
  //   filename: '[name].js'
  // }

  // 多个文件合并打包
  entry: [
    './entry1.js',
    './entry2.js'
  ],
  output: {
    filename: 'bundle.js'
  }
};
<|endoftext|>