<commit_before>Template.jobsListActions.rendered = function() {
  $('[data-toggle="tooltip"]').tooltip();
};

Template.jobsListActions.helpers({
  isAllActive: function() {
    var active = 0;
    var suspended = 0;
    this.forEach(function(item) {
      if (item.isActive) {
        active++;
      } else {
        suspended++;
      }
    });
    return active >= suspended;
  },
  isAllDetailsVisible: function() {
    var totalCount = this.count();
    var visibleCount = 0;
    this.forEach(function(item) {
      var currentItem = item._id;
      var currentItemState = Session.get("jobDetailState" + currentItem);
      if (typeof(currentItem) != 'undefined' && currentItemState === "details") {
        visibleCount++;
      }
    });//for each
    return visibleCount > totalCount/2;
  },
  isRecycle: function() {
    return Router.current().route.getName() === "jobsRecycle";
  }
});

Template.jobsListActions.events({
  'click #toggleJobDetails': function(e) {
    e.preventDefault();
    e.stopImmediatePropagation();

    var totalCount = this.count();
    var visibleCount = 0;
    this.forEach(function(item) {
      var currentItem = item._id;
      var currentItemState = Session.get("jobDetailState"+ currentItem);
      if (typeof(currentItem) != 'undefined' && currentItemState === "details") {
        visibleCount++;
      }
    });//for each
    if (visibleCount < totalCount/2) {
      this.forEach(function(item) {
        var currentItem = item._id;
        Session.set("jobDetailStateItem", currentItem);
        Session.set("jobDetailState"+ currentItem, "details");
        var $panels = $(".jobs .panel");
        $panels.each(function(index) {
          $($panels[index]).removeClass("collapsed").addClass("details");
        });
      });
    } else {
      this.forEach(function(item) {
        var currentItem = item._id;
        Session.set("jobDetailStateItem", currentItem);
        Session.set("jobDetailState"+ currentItem, "");
        var $panels = $(".jobs .panel");
        $panels.each(function(index) {
          $($panels[index]).removeClass("details").addClass("collapsed");
        });
      });
    }
  },

  'click #suspendAll': function(e) {
    e.preventDefault();
    e.stopImmediatePropagation();
    $(e.target).tooltip('hide');
    this.forEach(function(item) {
      item.isActive = false;
      Meteor.call('jobUpdate', item, function(error, result) {
        if (error) {
          return alert(error.reason);
        }
      });
    });
  },

  'click #resumeAll': function(e) {
    e.preventDefault();
    e.stopImmediatePropagation();
    $(e.target).tooltip('hide');
    this.forEach(function(item) {
      item.isActive = true;
      Meteor.call('jobUpdate', item, function(error, result) {
        if (error) {
          return alert(error.reason);
        }
      });
    });
  },

  'click #deleteAll': function(e) {
    e.preventDefault();
    e.stopImmediatePropagation();
    var jobs = this;
    bootbox.dialog({
      message: "Are you sure you want to delete all monitoring job? They can still be recovered later, if you change your mind. This will also hide all in the events view.",
      title: "Delete all monitoring jobs?",
      buttons: {
        success: {
          label: "Cancel",
          className: "btn-default",
          callback: function() {
            $('.bootbox').modal('hide');
          }
        },
        danger: {
          label: "Delete",
          className: "btn-danger",
          callback: function() {
            jobs.forEach(function(item) {
              item.isDeleted = true;
              Meteor.call('jobUpdate', item, function(error, result) {
                if (error) {
                  return alert(error.reason);
                }
              });
            });
          }
        }
      }
    });
  },

  'click #undoAll': function(e) {
    e.preventDefault();
    e.stopImmediatePropagation();
    this.forEach(function(item) {
      item.isDeleted = false;
      Meteor.call('jobRecoverDeleted', item, function(error, result) {
        if (error) {
          return alert(error.reason);
        }
      });
    });

  },

  'click #eraseAll': function(e) {
    e.preventDefault();
    e.stopImmediatePropagation();
    var jobs = this;
    bootbox.dialog({
      message: "Are you sure you want to delete all these monitoring jobs? They cannot be recovered later on.",
      title: "Delete all monitoring jobs?",
      buttons: {
        success: {
          label: "Cancel",
          className: "btn-default",
          callback: function() {
            $('.bootbox').modal('hide');
          }
        },
        danger: {
          label: "Delete",
          className: "btn-danger",
          callback: function() {
            jobs.forEach(function(item) {
              Jobs.remove(item._id);
            });
          }
        }
      }
    });
  }
});
<commit_msg>Fixed sticky tooltip issue<commit_after>Template.jobsListActions.rendered = function() {
  $('[data-toggle="tooltip"]').tooltip();
};

Template.jobsListActions.helpers({
  isAllActive: function() {
    var active = 0;
    var suspended = 0;
    this.forEach(function(item) {
      if (item.isActive) {
        active++;
      } else {
        suspended++;
      }
    });
    return active >= suspended;
  },
  isAllDetailsVisible: function() {
    var totalCount = this.count();
    var visibleCount = 0;
    this.forEach(function(item) {
      var currentItem = item._id;
      var currentItemState = Session.get("jobDetailState" + currentItem);
      if (typeof(currentItem) != 'undefined' && currentItemState === "details") {
        visibleCount++;
      }
    });//for each
    return visibleCount > totalCount/2;
  },
  isRecycle: function() {
    return Router.current().route.getName() === "jobsRecycle";
  }
});

Template.jobsListActions.events({
  'click #toggleJobDetails': function(e) {
    e.preventDefault();
    e.stopImmediatePropagation();

    var totalCount = this.count();
    var visibleCount = 0;
    this.forEach(function(item) {
      var currentItem = item._id;
      var currentItemState = Session.get("jobDetailState"+ currentItem);
      if (typeof(currentItem) != 'undefined' && currentItemState === "details") {
        visibleCount++;
      }
    });//for each
    if (visibleCount < totalCount/2) {
      this.forEach(function(item) {
        var currentItem = item._id;
        Session.set("jobDetailStateItem", currentItem);
        Session.set("jobDetailState"+ currentItem, "details");
        var $panels = $(".jobs .panel");
        $panels.each(function(index) {
          $($panels[index]).removeClass("collapsed").addClass("details");
        });
      });
    } else {
      this.forEach(function(item) {
        var currentItem = item._id;
        Session.set("jobDetailStateItem", currentItem);
        Session.set("jobDetailState"+ currentItem, "");
        var $panels = $(".jobs .panel");
        $panels.each(function(index) {
          $($panels[index]).removeClass("details").addClass("collapsed");
        });
      });
    }
  },

  'click #suspendAll': function(e) {
    $(e.currentTarget).tooltip('destroy');
    this.forEach(function(item) {
      item.isActive = false;
      Meteor.call('jobUpdate', item, function(error, result) {
        if (error) {
          return alert(error.reason);
        }
      });
    });
  },

  'click #resumeAll': function(e) {
    $(e.currentTarget).tooltip('destroy');
    this.forEach(function(item) {
      item.isActive = true;
      Meteor.call('jobUpdate', item, function(error, result) {
        if (error) {
          return alert(error.reason);
        }
      });
    });
  },

  'click #deleteAll': function(e) {
    e.preventDefault();
    e.stopImmediatePropagation();
    var jobs = this;
    bootbox.dialog({
      message: "Are you sure you want to delete all monitoring job? They can still be recovered later, if you change your mind. This will also hide all in the events view.",
      title: "Delete all monitoring jobs?",
      buttons: {
        success: {
          label: "Cancel",
          className: "btn-default",
          callback: function() {
            $('.bootbox').modal('hide');
          }
        },
        danger: {
          label: "Delete",
          className: "btn-danger",
          callback: function() {
            jobs.forEach(function(item) {
              item.isDeleted = true;
              Meteor.call('jobUpdate', item, function(error, result) {
                if (error) {
                  return alert(error.reason);
                }
              });
            });
          }
        }
      }
    });
  },

  'click #undoAll': function(e) {
    e.preventDefault();
    e.stopImmediatePropagation();
    this.forEach(function(item) {
      item.isDeleted = false;
      Meteor.call('jobRecoverDeleted', item, function(error, result) {
        if (error) {
          return alert(error.reason);
        }
      });
    });

  },

  'click #eraseAll': function(e) {
    e.preventDefault();
    e.stopImmediatePropagation();
    var jobs = this;
    bootbox.dialog({
      message: "Are you sure you want to delete all these monitoring jobs? They cannot be recovered later on.",
      title: "Delete all monitoring jobs?",
      buttons: {
        success: {
          label: "Cancel",
          className: "btn-default",
          callback: function() {
            $('.bootbox').modal('hide');
          }
        },
        danger: {
          label: "Delete",
          className: "btn-danger",
          callback: function() {
            jobs.forEach(function(item) {
              Jobs.remove(item._id);
            });
          }
        }
      }
    });
  }
});
<|endoftext|>


<commit_before>
describe('Test upload widget non-standard options', function () {
    it('create the widget', function () {
        runs(function () {
            var uploadWidget = new girder.views.UploadWidget({
                noParent: true,
                modal: false,
                title: null,
                el: 'body'
            }).render();

            expect($('.modal').length).toBe(0);
            expect($('#g-upload-form h4').length).toBe(0);
            expect($('.g-dialog-subtitle').length).toBe(0);
            expect($('.g-drop-zone:visible').length).toBe(1);
            expect($('.g-start-upload.btn.disabled:visible').length).toBe(1);
            expect($('.g-overall-progress-message').text()).toBe('No files selected');
        });
    });
});
<commit_msg>New fail regex caught a test :)<commit_after>
describe('Test upload widget non-standard options', function () {
    it('create the widget', function () {
        runs(function () {
            var uploadWidget = new girder.views.UploadWidget({
                noParent: true,
                modal: false,
                title: null,
                el: 'body',
                parentView: null
            }).render();

            expect($('.modal').length).toBe(0);
            expect($('#g-upload-form h4').length).toBe(0);
            expect($('.g-dialog-subtitle').length).toBe(0);
            expect($('.g-drop-zone:visible').length).toBe(1);
            expect($('.g-start-upload.btn.disabled:visible').length).toBe(1);
            expect($('.g-overall-progress-message').text()).toBe('No files selected');
        });
    });
});
<|endoftext|>


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

/**
 * @fileoverview A plugin to enable the First Strong Bidi algorithm.  The First
 * Strong algorithm as a heuristic used to automatically set paragraph direction
 * depending on its content.
 *
 * In the documentation below, a 'paragraph' is the local element which we
 * evaluate as a whole for purposes of determining directionality. It may be a
 * block-level element (e.g. <div>) or a whole list (e.g. <ul>).
 *
 * This implementation is based on, but is not identical to, the original
 * First Strong algorithm defined in Unicode
 * @see http://www.unicode.org/reports/tr9/
 * The central difference from the original First Strong algorithm is that this
 * implementation decides the paragraph direction based on the first strong
 * character that is typed into the paragraph, regardless of its
 * location in the paragraph, as opposed to the original algorithm where it is
 * the first character in the paragraph by location, regardless of
 * whether other strong characters already appear in the paragraph, further its
 * start.
 *
 * Please note that this plugin does not perform the direction change
 * itself. Rather, it fires editor commands upon the key up event when a
 * direction change needs to be performed; {@code goog.editor.Command.DIR_RTL}
 * or {@code goog.editor.Command.DIR_RTL}.
 *
 */

goog.provide('goog.editor.plugins.FirstStrong');

goog.require('goog.dom');
goog.require('goog.dom.NodeIterator');
goog.require('goog.dom.NodeType');
goog.require('goog.dom.TagName');
goog.require('goog.editor.Command');
goog.require('goog.editor.Plugin');
goog.require('goog.editor.node');
goog.require('goog.i18n.bidi');
goog.require('goog.i18n.uChar');
goog.require('goog.iter');
goog.require('goog.userAgent');

/**
 * First Strong plugin.
 * @constructor
 * @extends {goog.editor.Plugin}
 */
goog.editor.plugins.FirstStrong = function() {
  goog.base(this);

  /**
   * Indicates whether or not the cursor is in a paragraph we have not yet
   * finished evaluating for directionality. This is set to true whenever the
   * cursor is moved, and set to false after seeing a strong character in the
   * paragraph the cursor is currently in.
   *
   * @type {boolean}
   * @private
   */
  this.isNewBlock_ = true;

  /**
   * Indicates whether or not the current paragraph the cursor is in should be
   * set to Right-To-Left directionality.
   *
   * @type {boolean}
   * @private
   */
  this.switchToRtl_ = false;

  /**
   * Indicates whether or not the current paragraph the cursor is in should be
   * set to Left-To-Right directionality.
   *
   * @type {boolean}
   * @private
   */
  this.switchToLtr_ = false;
};
goog.inherits(goog.editor.plugins.FirstStrong, goog.editor.Plugin);

/** @override */
goog.editor.plugins.FirstStrong.prototype.getTrogClassId = function() {
  return 'FirstStrong';
};

/** @override */
goog.editor.plugins.FirstStrong.prototype.queryCommandValue =
    function(command) {
  return false;
};

/** @override */
goog.editor.plugins.FirstStrong.prototype.handleSelectionChange =
    function(e, node) {
  this.isNewBlock_ = true;
  return false;
};

/** @override */
goog.editor.plugins.FirstStrong.prototype.handleKeyPress = function(e) {
  if (!this.isNewBlock_) {
    return false;  // We've already determined this paragraph's direction.
  }
  var newChar = goog.i18n.uChar.fromCharCode(e.charCode);
  if (!newChar) {
    return false;  // Unrecognized key.
  }
  var isLtr = goog.i18n.bidi.isLtrChar(newChar);
  var isRtl = !isLtr && goog.i18n.bidi.isRtlChar(newChar);
  if (!isLtr && !isRtl) {
    return false;  // This character cannot change anything (it is not Strong).
  }
  // This character is Strongly LTR or Strongly RTL. We might switch direction
  // on it now, but in any case we do not need to check any more characters in
  // this paragraph after it.
  this.isNewBlock_ = false;

  // Are there no Strong characters already in the paragraph?
  if (this.isNeutralBlock_()) {
    this.switchToRtl_ = isRtl;
    this.switchToLtr_ = isLtr;
  }
  return false;
};

/**
 * Calls the flip directionality commands.  This is done here so things go into
 * the redo-undo stack at the expected order; fist enter the input, then flip
 * directionality.
 * @override
 */
goog.editor.plugins.FirstStrong.prototype.handleKeyUp = function(e) {
  if (this.switchToRtl_) {
    var field = this.getFieldObject();
    field.dispatchChange(true);
    field.execCommand(goog.editor.Command.DIR_RTL);
    this.switchToRtl_ = false;
  } else if (this.switchToLtr_) {
    var field = this.getFieldObject();
    field.dispatchChange(true);
    field.execCommand(goog.editor.Command.DIR_LTR);
    this.switchToLtr_ = false;
  }
  return false;
};

/**
 * @return {Element} The lowest Block element ancestor of the node where the
 *     next character will be placed.
 * @private
 */
goog.editor.plugins.FirstStrong.prototype.getBlockAncestor_ = function() {
  var start = this.getFieldObject().getRange().getStartNode();
  // Go up in the DOM until we reach a Block element.
  while (!goog.editor.plugins.FirstStrong.isBlock_(start)) {
    start = start.parentNode;
  }
  return /** @type {Element} */ start;
};

/**
 * @return {boolean} Whether the paragraph where the next character will be
 *     entered contains only non-Strong characters.
 * @private
 */
goog.editor.plugins.FirstStrong.prototype.isNeutralBlock_ = function() {
  var root = this.getBlockAncestor_();
  var cursor = this.getFieldObject().getRange().getStartNode();

  if (root == cursor) {  // This will be the beginning of a new text node.
    cursor = root.lastChild;
  }

  // In FireFox the BR tag also represents a change in paragraph if not inside a
  // list. So we need special handling to only look at the sub-block between
  // BR elements.
  var blockFunction = (goog.userAgent.GECKO &&
      !this.isList_(root)) ?
          goog.editor.plugins.FirstStrong.isGeckoBlock_ :
          goog.editor.plugins.FirstStrong.isBlock_;
  var paragraph = this.getTextAround_(root, cursor,
      blockFunction);
  // Not using {@code goog.i18n.bidi.isNeutralText} as it contains additional,
  // unwanted checks to the content.
  return !goog.i18n.bidi.hasAnyLtr(paragraph) &&
      !goog.i18n.bidi.hasAnyRtl(paragraph);
};

/**
 * Checks if an element is a list element ('UL' or 'OL').
 *
 * @param {Element} element The element to test.
 * @return {boolean} Whether the element is a list element ('UL' or 'OL').
 * @private
 */
goog.editor.plugins.FirstStrong.prototype.isList_ = function(element) {
  if (!element) {
    return false;
  }
  var tagName = element.tagName;
  return tagName == goog.dom.TagName.UL || tagName == goog.dom.TagName.OL;
};

/**
 * Returns the text within the local paragraph around the cursor.
 * Notice that for GECKO a BR represents a pargraph change despite not being a
 * block element.
 *
 * @param {Element} root The first block element ancestor of the node the cursor
 *     is in.
 * @param {Node} cursorLocation Node where the cursor currently is, marking the
 *     paragraph whose text we will return.
 * @param {function(Node): boolean} isParagraphBoundry The function to determine
 *     if a node represents the start or end of the paragraph.
 * @return {string} the text in the paragraph around the cursor location.
 * @private
 */
goog.editor.plugins.FirstStrong.prototype.getTextAround_ = function(root,
    cursorLocation, isParagraphBoundry) {
  // The buffer where we're collecting the text.
  var buffer = [];
  // Have we reached the cursor yet, or are we still before it?
  var pastCursorLocation = false;

  if (root && cursorLocation) {
    goog.iter.some(new goog.dom.NodeIterator(root), function(node) {
      if (node == cursorLocation) {
        pastCursorLocation = true;
      } else if (isParagraphBoundry(node)) {
        if (pastCursorLocation) {
          // This is the end of the paragraph containing the cursor. We're done.
          return true;
        } else {
          // All we collected so far does not count; it was in a previous
          // paragraph that did not contain the cursor.
          buffer = [];
        }
      }
      if (node.nodeType == goog.dom.NodeType.TEXT) {
        buffer.push(node.nodeValue);
      }
      return false;  // Keep going.
    });
  }
  return buffer.join('');
};

/**
 * @param {Node} node Node to check.
 * @return {boolean} Does the given node represent a Block element? Notice we do
 *     not consider list items as Block elements in the algorithm.
 * @private
 */
goog.editor.plugins.FirstStrong.isBlock_ = function(node) {
  return !!node && goog.editor.node.isBlockTag(node) &&
      node.tagName != goog.dom.TagName.LI;
};

/**
 * @param {Node} node Node to check.
 * @return {boolean} Does the given node represent a Block element from the
 *     point of view of FireFox? Notice we do not consider list items as Block
 *     elements in the algorithm.
 * @private
 */
goog.editor.plugins.FirstStrong.isGeckoBlock_ = function(node) {
  return !!node && (node.tagName == goog.dom.TagName.BR ||
      goog.editor.plugins.FirstStrong.isBlock_(node));
};
<commit_msg><commit_after>// Copyright 2012 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview A plugin to enable the First Strong Bidi algorithm.  The First
 * Strong algorithm as a heuristic used to automatically set paragraph direction
 * depending on its content.
 *
 * In the documentation below, a 'paragraph' is the local element which we
 * evaluate as a whole for purposes of determining directionality. It may be a
 * block-level element (e.g. <div>) or a whole list (e.g. <ul>).
 *
 * This implementation is based on, but is not identical to, the original
 * First Strong algorithm defined in Unicode
 * @see http://www.unicode.org/reports/tr9/
 * The central difference from the original First Strong algorithm is that this
 * implementation decides the paragraph direction based on the first strong
 * character that is typed into the paragraph, regardless of its
 * location in the paragraph, as opposed to the original algorithm where it is
 * the first character in the paragraph by location, regardless of
 * whether other strong characters already appear in the paragraph, further its
 * start.
 *
 * Please note that this plugin does not perform the direction change
 * itself. Rather, it fires editor commands upon the key up event when a
 * direction change needs to be performed; {@code goog.editor.Command.DIR_RTL}
 * or {@code goog.editor.Command.DIR_RTL}.
 *
 */

goog.provide('goog.editor.plugins.FirstStrong');

goog.require('goog.dom');
goog.require('goog.dom.NodeIterator');
goog.require('goog.dom.NodeType');
goog.require('goog.dom.TagName');
goog.require('goog.editor.Command');
goog.require('goog.editor.Plugin');
goog.require('goog.editor.node');
goog.require('goog.i18n.bidi');
goog.require('goog.i18n.uChar');
goog.require('goog.iter');
goog.require('goog.userAgent');

/**
 * First Strong plugin.
 * @constructor
 * @extends {goog.editor.Plugin}
 */
goog.editor.plugins.FirstStrong = function() {
  goog.base(this);

  /**
   * Indicates whether or not the cursor is in a paragraph we have not yet
   * finished evaluating for directionality. This is set to true whenever the
   * cursor is moved, and set to false after seeing a strong character in the
   * paragraph the cursor is currently in.
   *
   * @type {boolean}
   * @private
   */
  this.isNewBlock_ = true;

  /**
   * Indicates whether or not the current paragraph the cursor is in should be
   * set to Right-To-Left directionality.
   *
   * @type {boolean}
   * @private
   */
  this.switchToRtl_ = false;

  /**
   * Indicates whether or not the current paragraph the cursor is in should be
   * set to Left-To-Right directionality.
   *
   * @type {boolean}
   * @private
   */
  this.switchToLtr_ = false;
};
goog.inherits(goog.editor.plugins.FirstStrong, goog.editor.Plugin);

/** @override */
goog.editor.plugins.FirstStrong.prototype.getTrogClassId = function() {
  return 'FirstStrong';
};

/** @override */
goog.editor.plugins.FirstStrong.prototype.queryCommandValue =
    function(command) {
  return false;
};

/** @override */
goog.editor.plugins.FirstStrong.prototype.handleSelectionChange =
    function(e, node) {
  this.isNewBlock_ = true;
  return false;
};

/** @override */
goog.editor.plugins.FirstStrong.prototype.handleKeyPress = function(e) {
  if (!this.isNewBlock_) {
    return false;  // We've already determined this paragraph's direction.
  }
  var newChar = goog.i18n.uChar.fromCharCode(e.charCode);
  if (!newChar) {
    return false;  // Unrecognized key.
  }
  var isLtr = goog.i18n.bidi.isLtrChar(newChar);
  var isRtl = !isLtr && goog.i18n.bidi.isRtlChar(newChar);
  if (!isLtr && !isRtl) {
    return false;  // This character cannot change anything (it is not Strong).
  }
  // This character is Strongly LTR or Strongly RTL. We might switch direction
  // on it now, but in any case we do not need to check any more characters in
  // this paragraph after it.
  this.isNewBlock_ = false;

  // Are there no Strong characters already in the paragraph?
  if (this.isNeutralBlock_()) {
    this.switchToRtl_ = isRtl;
    this.switchToLtr_ = isLtr;
  }
  return false;
};

/**
 * Calls the flip directionality commands.  This is done here so things go into
 * the redo-undo stack at the expected order; fist enter the input, then flip
 * directionality.
 * @override
 */
goog.editor.plugins.FirstStrong.prototype.handleKeyUp = function(e) {
  if (this.switchToRtl_) {
    var field = this.getFieldObject();
    field.dispatchChange(true);
    field.execCommand(goog.editor.Command.DIR_RTL);
    this.switchToRtl_ = false;
  } else if (this.switchToLtr_) {
    var field = this.getFieldObject();
    field.dispatchChange(true);
    field.execCommand(goog.editor.Command.DIR_LTR);
    this.switchToLtr_ = false;
  }
  return false;
};

/**
 * @return {Element} The lowest Block element ancestor of the node where the
 *     next character will be placed.
 * @private
 */
goog.editor.plugins.FirstStrong.prototype.getBlockAncestor_ = function() {
  var start = this.getFieldObject().getRange().getStartNode();
  // Go up in the DOM until we reach a Block element.
  while (!goog.editor.plugins.FirstStrong.isBlock_(start)) {
    start = start.parentNode;
  }
  return /** @type {Element} */ (start);
};

/**
 * @return {boolean} Whether the paragraph where the next character will be
 *     entered contains only non-Strong characters.
 * @private
 */
goog.editor.plugins.FirstStrong.prototype.isNeutralBlock_ = function() {
  var root = this.getBlockAncestor_();
  var cursor = this.getFieldObject().getRange().getStartNode();

  if (root == cursor) {  // This will be the beginning of a new text node.
    cursor = root.lastChild;
  }

  // In FireFox the BR tag also represents a change in paragraph if not inside a
  // list. So we need special handling to only look at the sub-block between
  // BR elements.
  var blockFunction = (goog.userAgent.GECKO &&
      !this.isList_(root)) ?
          goog.editor.plugins.FirstStrong.isGeckoBlock_ :
          goog.editor.plugins.FirstStrong.isBlock_;
  var paragraph = this.getTextAround_(root, cursor,
      blockFunction);
  // Not using {@code goog.i18n.bidi.isNeutralText} as it contains additional,
  // unwanted checks to the content.
  return !goog.i18n.bidi.hasAnyLtr(paragraph) &&
      !goog.i18n.bidi.hasAnyRtl(paragraph);
};

/**
 * Checks if an element is a list element ('UL' or 'OL').
 *
 * @param {Element} element The element to test.
 * @return {boolean} Whether the element is a list element ('UL' or 'OL').
 * @private
 */
goog.editor.plugins.FirstStrong.prototype.isList_ = function(element) {
  if (!element) {
    return false;
  }
  var tagName = element.tagName;
  return tagName == goog.dom.TagName.UL || tagName == goog.dom.TagName.OL;
};

/**
 * Returns the text within the local paragraph around the cursor.
 * Notice that for GECKO a BR represents a pargraph change despite not being a
 * block element.
 *
 * @param {Element} root The first block element ancestor of the node the cursor
 *     is in.
 * @param {Node} cursorLocation Node where the cursor currently is, marking the
 *     paragraph whose text we will return.
 * @param {function(Node): boolean} isParagraphBoundry The function to determine
 *     if a node represents the start or end of the paragraph.
 * @return {string} the text in the paragraph around the cursor location.
 * @private
 */
goog.editor.plugins.FirstStrong.prototype.getTextAround_ = function(root,
    cursorLocation, isParagraphBoundry) {
  // The buffer where we're collecting the text.
  var buffer = [];
  // Have we reached the cursor yet, or are we still before it?
  var pastCursorLocation = false;

  if (root && cursorLocation) {
    goog.iter.some(new goog.dom.NodeIterator(root), function(node) {
      if (node == cursorLocation) {
        pastCursorLocation = true;
      } else if (isParagraphBoundry(node)) {
        if (pastCursorLocation) {
          // This is the end of the paragraph containing the cursor. We're done.
          return true;
        } else {
          // All we collected so far does not count; it was in a previous
          // paragraph that did not contain the cursor.
          buffer = [];
        }
      }
      if (node.nodeType == goog.dom.NodeType.TEXT) {
        buffer.push(node.nodeValue);
      }
      return false;  // Keep going.
    });
  }
  return buffer.join('');
};

/**
 * @param {Node} node Node to check.
 * @return {boolean} Does the given node represent a Block element? Notice we do
 *     not consider list items as Block elements in the algorithm.
 * @private
 */
goog.editor.plugins.FirstStrong.isBlock_ = function(node) {
  return !!node && goog.editor.node.isBlockTag(node) &&
      node.tagName != goog.dom.TagName.LI;
};

/**
 * @param {Node} node Node to check.
 * @return {boolean} Does the given node represent a Block element from the
 *     point of view of FireFox? Notice we do not consider list items as Block
 *     elements in the algorithm.
 * @private
 */
goog.editor.plugins.FirstStrong.isGeckoBlock_ = function(node) {
  return !!node && (node.tagName == goog.dom.TagName.BR ||
      goog.editor.plugins.FirstStrong.isBlock_(node));
};
<|endoftext|>


<commit_before>// TODO: Remove this eslint exception when implementing TypeScript.
/* eslint-disable react/prop-types */
import { Auth0Provider } from '@auth0/auth0-react'
import { Col, Grid, Row } from 'react-bootstrap'
import { connect } from 'react-redux'
import { ConnectedRouter } from 'connected-react-router'
import { createHashHistory } from 'history'
import { injectIntl, IntlProvider } from 'react-intl'
import { MapProvider } from 'react-map-gl'
import { Route, Switch, withRouter } from 'react-router'
import coreUtils from '@opentripplanner/core-utils'
import isEqual from 'lodash.isequal'
import PropTypes from 'prop-types'
import qs from 'qs'
import React, { Component } from 'react'

import * as authActions from '../../actions/auth'
import * as callTakerActions from '../../actions/call-taker'
import * as formActions from '../../actions/form'
import * as locationActions from '../../actions/location'
import * as mapActions from '../../actions/map'
import * as uiActions from '../../actions/ui'
import { AUTH0_AUDIENCE, AUTH0_SCOPE } from '../../util/constants'
import { ComponentContext } from '../../util/contexts'
import { getActiveItinerary, getTitle } from '../../util/state'
import { getAuth0Config } from '../../util/auth'
import { getDefaultLocale } from '../../util/i18n'
import BeforeSignInScreen from '../user/before-signin-screen'
import Map from '../map/map'
import MobileMain from '../mobile/main'
import printRoutes from '../../util/webapp-print-routes'
import webAppRoutes from '../../util/webapp-routes'
import withLoggedInUserSupport from '../user/with-logged-in-user-support'
import withMap from '../map/with-map'

import DesktopNav from './desktop-nav'
import PopupWrapper from './popup'
import SessionTimeout from './session-timeout'

const { isMobile } = coreUtils.ui

const routes = [...webAppRoutes, ...printRoutes]

class ResponsiveWebapp extends Component {
  static propTypes = {
    initZoomOnLocate: PropTypes.number,
    query: PropTypes.object
  }

  static contextType = ComponentContext

  /** Lifecycle methods **/

  /* eslint-disable-next-line complexity */
  componentDidUpdate(prevProps) {
    const {
      activeSearchId,
      currentPosition,
      formChanged,
      initZoomOnLocate,
      intl,
      location,
      map,
      matchContentToUrl,
      query,
      setLocationToCurrent,
      setMapCenter,
      title
    } = this.props
    document.title = title
    const urlParams = coreUtils.query.getUrlParams()
    const newSearchId = urlParams.ui_activeSearch
    // Determine if trip is being replanned by checking the active search ID
    // against the ID found in the URL params. If they are different, a new one
    // has been routed to (see handleBackButtonPress) and there is no need to
    // trigger a form change because necessarily the query will be different
    // from the previous query.
    const replanningTrip =
      newSearchId && activeSearchId && newSearchId !== activeSearchId
    if (!isEqual(prevProps.query, query) && !replanningTrip) {
      // Trigger on form change action if previous query is different from
      // current one AND trip is not being replanned already. This will
      // determine whether a search needs to be made, the mobile view needs
      // updating, etc.
      formChanged(prevProps.query, query)
    }

    // check if device position changed (typically only set once, on initial page load)
    if (currentPosition !== prevProps.currentPosition) {
      if (currentPosition.error || !currentPosition.coords) return
      const pt = {
        lat: currentPosition.coords.latitude,
        lon: currentPosition.coords.longitude
      }

      // if in mobile mode and from field is not set, use current location as from and recenter map
      if (isMobile() && query.from === null) {
        setLocationToCurrent({ locationType: 'from' }, intl)
        setMapCenter(map, pt, initZoomOnLocate)
      }
    }
    // If the path changes (e.g., via a back button press) check whether the
    // main content needs to switch between, for example, a viewer and a search.
    if (!isEqual(location.pathname, prevProps.location.pathname)) {
      // console.log('url changed to', location.pathname)
      matchContentToUrl(map, location)
    }

    // Check for change between ITINERARY and PROFILE routingTypes
    // TODO: restore this for profile mode
    /* if (query.routingType !== nextProps.query.routingType) {
      let queryModes = nextProps.query.mode.split(',')
      // If we are entering 'ITINERARY' mode, ensure that one and only one access mode is selected
      if (nextProps.query.routingType === 'ITINERARY') {
        queryModes = ensureSingleAccessMode(queryModes)
        this.props.setQueryParam({ mode: queryModes.join(',') })
      }
      // If we are entering 'PROFILE' mode, ensure that CAR_HAIL is not selected
      // TODO: make this more generic, i.e. introduce concept of mode->routingType permissions
      if (nextProps.query.routingType === 'ITINERARY') {
        queryModes = queryModes.filter(mode => mode !== 'CAR_HAIL')
        this.props.setQueryParam({ mode: queryModes.join(',') })
      }
    } */
  }

  componentDidMount() {
    const {
      getCurrentPosition,
      handleBackButtonPress,
      initializeModules,
      intl,
      location,
      map,
      matchContentToUrl,
      parseUrlQueryString,
      receivedPositionResponse,
      title
    } = this.props
    // Add on back button press behavior.
    window.addEventListener('popstate', handleBackButtonPress)
    document.title = title

    // If a URL is detected without hash routing (e.g., http://localhost:9966?sessionId=test),
    // window.location.search will have a value. In this case, we need to redirect to the URL root with the
    // search reconstructed for use with the hash router.
    // Exception: Do not redirect after auth0 login, which sets the URL in the form
    // http://localhost:9966/?code=xxxxxxx&state=yyyyyyyyy that we want to preserve.
    const search = window.location.search
    if (search) {
      const searchParams = qs.parse(search, { ignoreQueryPrefix: true })
      if (!(searchParams.code && searchParams.state)) {
        window.location.href = `${window.location.origin}/#/${search}`
        return
      }
    }

    if (isMobile()) {
      // If on mobile browser, check position on load
      getCurrentPosition(intl)

      // Also, watch for changes in position on mobile
      navigator.geolocation.watchPosition(
        // On success
        (position) => {
          receivedPositionResponse({ position: { ...position } })
        },
        // On error
        (error) => {
          console.log('error in watchPosition', error)
        },
        // Options
        { enableHighAccuracy: true }
      )
    }
    // Handle routing to a specific part of the app (e.g. stop viewer) on page
    // load. (This happens prior to routing request in case special routerId is
    // set from URL.)
    matchContentToUrl(map, location)
    if (location && location.search) {
      // Set search params and plan trip if routing enabled and a query exists
      // in the URL.
      parseUrlQueryString()
    }
    // Initialize call taker/field trip modules (check for valid auth session).
    initializeModules(intl)
  }

  componentWillUnmount() {
    // Remove on back button press listener.
    window.removeEventListener('popstate', this.props.handleBackButtonPress)
  }

  _hidePopup = () => {
    const { setPopupContent } = this?.props
    if (setPopupContent) setPopupContent(null)
  }

  renderDesktopView = () => {
    const { sessionTimeoutSeconds } = this.props
    const { MainControls, MainPanel, MapWindows } = this.context
    const { popupContent } = this.props
    return (
      <div className="otp">
        <DesktopNav />
        <PopupWrapper content={popupContent} hideModal={this._hidePopup} />
        <Grid>
          <Row className="main-row">
            <Col className="sidebar" md={4} sm={6}>
              {/*
                Note: the main tag provides a way for users of screen readers
                to skip to the primary page content.
                TODO: Find a better place.
              */}
              <main>
                <MainPanel />
              </main>
            </Col>
            {MainControls && <MainControls />}
            <Col className="map-container" md={8} sm={6}>
              {MapWindows && <MapWindows />}
              <Map />
            </Col>
          </Row>
        </Grid>
        {sessionTimeoutSeconds && <SessionTimeout />}
      </div>
    )
  }

  renderMobileView = () => {
    const { popupContent } = this.props

    return (
      // <main> Needed for accessibility checks. TODO: Find a better place.
      <main>
        <PopupWrapper content={popupContent} hideModal={this._hidePopup} />
        <MobileMain />
      </main>
    )
  }

  render() {
    return isMobile() ? this.renderMobileView() : this.renderDesktopView()
  }
}

// connect to the redux store

const mapStateToProps = (state, ownProps) => {
  const title = getTitle(state, ownProps.intl)
  return {
    activeItinerary: getActiveItinerary(state),
    activeSearchId: state.otp.activeSearchId,
    currentPosition: state.otp.location.currentPosition,
    initZoomOnLocate:
      state.otp.config.map && state.otp.config.map.initZoomOnLocate,
    locale: state.otp.ui.locale,
    mobileScreen: state.otp.ui.mobileScreen,
    modeGroups: state.otp.config.modeGroups,
    popupContent: state.otp.ui.popup,
    query: state.otp.currentQuery,
    searches: state.otp.searches,
    sessionTimeoutSeconds: state.otp.config.sessionTimeoutSeconds,
    title
  }
}

const mapDispatchToProps = {
  formChanged: formActions.formChanged,
  getCurrentPosition: locationActions.getCurrentPosition,
  handleBackButtonPress: uiActions.handleBackButtonPress,
  initializeModules: callTakerActions.initializeModules,
  matchContentToUrl: uiActions.matchContentToUrl,
  parseUrlQueryString: formActions.parseUrlQueryString,
  receivedPositionResponse: locationActions.receivedPositionResponse,
  setLocationToCurrent: mapActions.setLocationToCurrent,
  setMapCenter: mapActions.setMapCenter,
  setPopupContent: uiActions.setPopupContent
}

const history = createHashHistory()

const WebappWithRouter = withRouter(
  withLoggedInUserSupport(
    withMap(
      injectIntl(connect(mapStateToProps, mapDispatchToProps)(ResponsiveWebapp))
    )
  )
)

/**
 * The routing component for the application.
 * This is the top-most "standard" component,
 * and we initialize the Auth0Provider here
 * so that Auth0 services are available everywhere.
 */
class RouterWrapperWithAuth0 extends Component {
  constructor(props) {
    super(props)
    this._initializeOrUpdateLocale(props)
  }

  componentDidUpdate(prevProps) {
    this._initializeOrUpdateLocale(this.props, prevProps)
  }

  /**
   * On component initialization, or if the URL locale parameter
   * changes or is initially blank, (e.g., user modifies anything after ?, e.g. locale)
   * update the corresponding redux state.
   * @param {*} props The current props for the component
   * @param {*} prevProps Optional previous props, if available.
   */
  _initializeOrUpdateLocale(props, prevProps) {
    const urlParams = coreUtils.query.getUrlParams()
    const { locale: newLocale } = urlParams

    if (
      !prevProps ||
      (!newLocale && !prevProps.locale) ||
      (newLocale && newLocale !== prevProps.locale)
    ) {
      props.setLocale(newLocale)
    }
  }

  render() {
    const {
      auth0Config,
      components,
      defaultLocale,
      homeTimezone,
      locale,
      localizedMessages,
      processSignIn,
      routerConfig,
      showAccessTokenError,
      showLoginError
    } = this.props

    // Don't render anything until the locale/localized messages have been initialized.
    const router = localizedMessages && (
      <ComponentContext.Provider value={components}>
        <MapProvider>
          <IntlProvider
            defaultLocale={defaultLocale}
            locale={locale || defaultLocale}
            messages={localizedMessages}
            timeZone={homeTimezone}
          >
            <ConnectedRouter
              basename={routerConfig && routerConfig.basename}
              history={history}
            >
              <Switch>
                {routes.map((props, index) => {
                  const {
                    getContextComponent,
                    shouldRenderWebApp,
                    ...routerProps
                  } = props

                  return (
                    <Route
                      component={
                        getContextComponent
                          ? getContextComponent(components)
                          : undefined
                      }
                      key={index}
                      render={
                        shouldRenderWebApp
                          ? () => <WebappWithRouter {...this.props} />
                          : undefined
                      }
                      {...routerProps}
                    />
                  )
                })}
                {/* For any other route, simply return the web app. */}
                <Route render={() => <WebappWithRouter {...this.props} />} />
              </Switch>
            </ConnectedRouter>
          </IntlProvider>
        </MapProvider>
      </ComponentContext.Provider>
    )

    return auth0Config ? (
      <Auth0Provider
        audience={AUTH0_AUDIENCE}
        clientId={auth0Config.clientId}
        domain={auth0Config.domain}
        onAccessTokenError={showAccessTokenError}
        onLoginError={showLoginError}
        onRedirectCallback={processSignIn}
        onRedirecting={BeforeSignInScreen}
        redirectUri={window.location.origin}
        scope={AUTH0_SCOPE}
      >
        {router}
      </Auth0Provider>
    ) : (
      router
    )
  }
}

const mapStateToWrapperProps = (state) => {
  const { homeTimezone, persistence, reactRouter } = state.otp.config
  return {
    auth0Config: getAuth0Config(persistence),
    defaultLocale: getDefaultLocale(state.otp.config, state.user.loggedInUser),
    homeTimezone,
    locale: state.otp.ui.locale,
    localizedMessages: state.otp.ui.localizedMessages,
    routerConfig: reactRouter
  }
}

const mapWrapperDispatchToProps = {
  processSignIn: authActions.processSignIn,
  setLocale: uiActions.setLocale,
  showAccessTokenError: authActions.showAccessTokenError,
  showLoginError: authActions.showLoginError
}

export default connect(
  mapStateToWrapperProps,
  mapWrapperDispatchToProps
)(RouterWrapperWithAuth0)
<commit_msg>refactor: add clarification for wild object cloning<commit_after>// TODO: Remove this eslint exception when implementing TypeScript.
/* eslint-disable react/prop-types */
import { Auth0Provider } from '@auth0/auth0-react'
import { Col, Grid, Row } from 'react-bootstrap'
import { connect } from 'react-redux'
import { ConnectedRouter } from 'connected-react-router'
import { createHashHistory } from 'history'
import { injectIntl, IntlProvider } from 'react-intl'
import { MapProvider } from 'react-map-gl'
import { Route, Switch, withRouter } from 'react-router'
import coreUtils from '@opentripplanner/core-utils'
import isEqual from 'lodash.isequal'
import PropTypes from 'prop-types'
import qs from 'qs'
import React, { Component } from 'react'

import * as authActions from '../../actions/auth'
import * as callTakerActions from '../../actions/call-taker'
import * as formActions from '../../actions/form'
import * as locationActions from '../../actions/location'
import * as mapActions from '../../actions/map'
import * as uiActions from '../../actions/ui'
import { AUTH0_AUDIENCE, AUTH0_SCOPE } from '../../util/constants'
import { ComponentContext } from '../../util/contexts'
import { getActiveItinerary, getTitle } from '../../util/state'
import { getAuth0Config } from '../../util/auth'
import { getDefaultLocale } from '../../util/i18n'
import BeforeSignInScreen from '../user/before-signin-screen'
import Map from '../map/map'
import MobileMain from '../mobile/main'
import printRoutes from '../../util/webapp-print-routes'
import webAppRoutes from '../../util/webapp-routes'
import withLoggedInUserSupport from '../user/with-logged-in-user-support'
import withMap from '../map/with-map'

import DesktopNav from './desktop-nav'
import PopupWrapper from './popup'
import SessionTimeout from './session-timeout'

const { isMobile } = coreUtils.ui

const routes = [...webAppRoutes, ...printRoutes]

class ResponsiveWebapp extends Component {
  static propTypes = {
    initZoomOnLocate: PropTypes.number,
    query: PropTypes.object
  }

  static contextType = ComponentContext

  /** Lifecycle methods **/

  /* eslint-disable-next-line complexity */
  componentDidUpdate(prevProps) {
    const {
      activeSearchId,
      currentPosition,
      formChanged,
      initZoomOnLocate,
      intl,
      location,
      map,
      matchContentToUrl,
      query,
      setLocationToCurrent,
      setMapCenter,
      title
    } = this.props
    document.title = title
    const urlParams = coreUtils.query.getUrlParams()
    const newSearchId = urlParams.ui_activeSearch
    // Determine if trip is being replanned by checking the active search ID
    // against the ID found in the URL params. If they are different, a new one
    // has been routed to (see handleBackButtonPress) and there is no need to
    // trigger a form change because necessarily the query will be different
    // from the previous query.
    const replanningTrip =
      newSearchId && activeSearchId && newSearchId !== activeSearchId
    if (!isEqual(prevProps.query, query) && !replanningTrip) {
      // Trigger on form change action if previous query is different from
      // current one AND trip is not being replanned already. This will
      // determine whether a search needs to be made, the mobile view needs
      // updating, etc.
      formChanged(prevProps.query, query)
    }

    // check if device position changed (typically only set once, on initial page load)
    if (currentPosition !== prevProps.currentPosition) {
      if (currentPosition.error || !currentPosition.coords) return
      const pt = {
        lat: currentPosition.coords.latitude,
        lon: currentPosition.coords.longitude
      }

      // if in mobile mode and from field is not set, use current location as from and recenter map
      if (isMobile() && query.from === null) {
        setLocationToCurrent({ locationType: 'from' }, intl)
        setMapCenter(map, pt, initZoomOnLocate)
      }
    }
    // If the path changes (e.g., via a back button press) check whether the
    // main content needs to switch between, for example, a viewer and a search.
    if (!isEqual(location.pathname, prevProps.location.pathname)) {
      // console.log('url changed to', location.pathname)
      matchContentToUrl(map, location)
    }

    // Check for change between ITINERARY and PROFILE routingTypes
    // TODO: restore this for profile mode
    /* if (query.routingType !== nextProps.query.routingType) {
      let queryModes = nextProps.query.mode.split(',')
      // If we are entering 'ITINERARY' mode, ensure that one and only one access mode is selected
      if (nextProps.query.routingType === 'ITINERARY') {
        queryModes = ensureSingleAccessMode(queryModes)
        this.props.setQueryParam({ mode: queryModes.join(',') })
      }
      // If we are entering 'PROFILE' mode, ensure that CAR_HAIL is not selected
      // TODO: make this more generic, i.e. introduce concept of mode->routingType permissions
      if (nextProps.query.routingType === 'ITINERARY') {
        queryModes = queryModes.filter(mode => mode !== 'CAR_HAIL')
        this.props.setQueryParam({ mode: queryModes.join(',') })
      }
    } */
  }

  componentDidMount() {
    const {
      getCurrentPosition,
      handleBackButtonPress,
      initializeModules,
      intl,
      location,
      map,
      matchContentToUrl,
      parseUrlQueryString,
      receivedPositionResponse,
      title
    } = this.props
    // Add on back button press behavior.
    window.addEventListener('popstate', handleBackButtonPress)
    document.title = title

    // If a URL is detected without hash routing (e.g., http://localhost:9966?sessionId=test),
    // window.location.search will have a value. In this case, we need to redirect to the URL root with the
    // search reconstructed for use with the hash router.
    // Exception: Do not redirect after auth0 login, which sets the URL in the form
    // http://localhost:9966/?code=xxxxxxx&state=yyyyyyyyy that we want to preserve.
    const search = window.location.search
    if (search) {
      const searchParams = qs.parse(search, { ignoreQueryPrefix: true })
      if (!(searchParams.code && searchParams.state)) {
        window.location.href = `${window.location.origin}/#/${search}`
        return
      }
    }

    if (isMobile()) {
      // If on mobile browser, check position on load
      getCurrentPosition(intl)

      // Also, watch for changes in position on mobile
      navigator.geolocation.watchPosition(
        // On success
        (position) => {
          // This object cloning is required to be allowed to read the position info twice
          // on webkit browsers.
          // See https://github.com/opentripplanner/otp-react-redux/pull/697 for details
          receivedPositionResponse({ position: { ...position } })
        },
        // On error
        (error) => {
          console.log('error in watchPosition', error)
        },
        // Options
        { enableHighAccuracy: true }
      )
    }
    // Handle routing to a specific part of the app (e.g. stop viewer) on page
    // load. (This happens prior to routing request in case special routerId is
    // set from URL.)
    matchContentToUrl(map, location)
    if (location && location.search) {
      // Set search params and plan trip if routing enabled and a query exists
      // in the URL.
      parseUrlQueryString()
    }
    // Initialize call taker/field trip modules (check for valid auth session).
    initializeModules(intl)
  }

  componentWillUnmount() {
    // Remove on back button press listener.
    window.removeEventListener('popstate', this.props.handleBackButtonPress)
  }

  _hidePopup = () => {
    const { setPopupContent } = this?.props
    if (setPopupContent) setPopupContent(null)
  }

  renderDesktopView = () => {
    const { sessionTimeoutSeconds } = this.props
    const { MainControls, MainPanel, MapWindows } = this.context
    const { popupContent } = this.props
    return (
      <div className="otp">
        <DesktopNav />
        <PopupWrapper content={popupContent} hideModal={this._hidePopup} />
        <Grid>
          <Row className="main-row">
            <Col className="sidebar" md={4} sm={6}>
              {/*
                Note: the main tag provides a way for users of screen readers
                to skip to the primary page content.
                TODO: Find a better place.
              */}
              <main>
                <MainPanel />
              </main>
            </Col>
            {MainControls && <MainControls />}
            <Col className="map-container" md={8} sm={6}>
              {MapWindows && <MapWindows />}
              <Map />
            </Col>
          </Row>
        </Grid>
        {sessionTimeoutSeconds && <SessionTimeout />}
      </div>
    )
  }

  renderMobileView = () => {
    const { popupContent } = this.props

    return (
      // <main> Needed for accessibility checks. TODO: Find a better place.
      <main>
        <PopupWrapper content={popupContent} hideModal={this._hidePopup} />
        <MobileMain />
      </main>
    )
  }

  render() {
    return isMobile() ? this.renderMobileView() : this.renderDesktopView()
  }
}

// connect to the redux store

const mapStateToProps = (state, ownProps) => {
  const title = getTitle(state, ownProps.intl)
  return {
    activeItinerary: getActiveItinerary(state),
    activeSearchId: state.otp.activeSearchId,
    currentPosition: state.otp.location.currentPosition,
    initZoomOnLocate:
      state.otp.config.map && state.otp.config.map.initZoomOnLocate,
    locale: state.otp.ui.locale,
    mobileScreen: state.otp.ui.mobileScreen,
    modeGroups: state.otp.config.modeGroups,
    popupContent: state.otp.ui.popup,
    query: state.otp.currentQuery,
    searches: state.otp.searches,
    sessionTimeoutSeconds: state.otp.config.sessionTimeoutSeconds,
    title
  }
}

const mapDispatchToProps = {
  formChanged: formActions.formChanged,
  getCurrentPosition: locationActions.getCurrentPosition,
  handleBackButtonPress: uiActions.handleBackButtonPress,
  initializeModules: callTakerActions.initializeModules,
  matchContentToUrl: uiActions.matchContentToUrl,
  parseUrlQueryString: formActions.parseUrlQueryString,
  receivedPositionResponse: locationActions.receivedPositionResponse,
  setLocationToCurrent: mapActions.setLocationToCurrent,
  setMapCenter: mapActions.setMapCenter,
  setPopupContent: uiActions.setPopupContent
}

const history = createHashHistory()

const WebappWithRouter = withRouter(
  withLoggedInUserSupport(
    withMap(
      injectIntl(connect(mapStateToProps, mapDispatchToProps)(ResponsiveWebapp))
    )
  )
)

/**
 * The routing component for the application.
 * This is the top-most "standard" component,
 * and we initialize the Auth0Provider here
 * so that Auth0 services are available everywhere.
 */
class RouterWrapperWithAuth0 extends Component {
  constructor(props) {
    super(props)
    this._initializeOrUpdateLocale(props)
  }

  componentDidUpdate(prevProps) {
    this._initializeOrUpdateLocale(this.props, prevProps)
  }

  /**
   * On component initialization, or if the URL locale parameter
   * changes or is initially blank, (e.g., user modifies anything after ?, e.g. locale)
   * update the corresponding redux state.
   * @param {*} props The current props for the component
   * @param {*} prevProps Optional previous props, if available.
   */
  _initializeOrUpdateLocale(props, prevProps) {
    const urlParams = coreUtils.query.getUrlParams()
    const { locale: newLocale } = urlParams

    if (
      !prevProps ||
      (!newLocale && !prevProps.locale) ||
      (newLocale && newLocale !== prevProps.locale)
    ) {
      props.setLocale(newLocale)
    }
  }

  render() {
    const {
      auth0Config,
      components,
      defaultLocale,
      homeTimezone,
      locale,
      localizedMessages,
      processSignIn,
      routerConfig,
      showAccessTokenError,
      showLoginError
    } = this.props

    // Don't render anything until the locale/localized messages have been initialized.
    const router = localizedMessages && (
      <ComponentContext.Provider value={components}>
        <MapProvider>
          <IntlProvider
            defaultLocale={defaultLocale}
            locale={locale || defaultLocale}
            messages={localizedMessages}
            timeZone={homeTimezone}
          >
            <ConnectedRouter
              basename={routerConfig && routerConfig.basename}
              history={history}
            >
              <Switch>
                {routes.map((props, index) => {
                  const {
                    getContextComponent,
                    shouldRenderWebApp,
                    ...routerProps
                  } = props

                  return (
                    <Route
                      component={
                        getContextComponent
                          ? getContextComponent(components)
                          : undefined
                      }
                      key={index}
                      render={
                        shouldRenderWebApp
                          ? () => <WebappWithRouter {...this.props} />
                          : undefined
                      }
                      {...routerProps}
                    />
                  )
                })}
                {/* For any other route, simply return the web app. */}
                <Route render={() => <WebappWithRouter {...this.props} />} />
              </Switch>
            </ConnectedRouter>
          </IntlProvider>
        </MapProvider>
      </ComponentContext.Provider>
    )

    return auth0Config ? (
      <Auth0Provider
        audience={AUTH0_AUDIENCE}
        clientId={auth0Config.clientId}
        domain={auth0Config.domain}
        onAccessTokenError={showAccessTokenError}
        onLoginError={showLoginError}
        onRedirectCallback={processSignIn}
        onRedirecting={BeforeSignInScreen}
        redirectUri={window.location.origin}
        scope={AUTH0_SCOPE}
      >
        {router}
      </Auth0Provider>
    ) : (
      router
    )
  }
}

const mapStateToWrapperProps = (state) => {
  const { homeTimezone, persistence, reactRouter } = state.otp.config
  return {
    auth0Config: getAuth0Config(persistence),
    defaultLocale: getDefaultLocale(state.otp.config, state.user.loggedInUser),
    homeTimezone,
    locale: state.otp.ui.locale,
    localizedMessages: state.otp.ui.localizedMessages,
    routerConfig: reactRouter
  }
}

const mapWrapperDispatchToProps = {
  processSignIn: authActions.processSignIn,
  setLocale: uiActions.setLocale,
  showAccessTokenError: authActions.showAccessTokenError,
  showLoginError: authActions.showLoginError
}

export default connect(
  mapStateToWrapperProps,
  mapWrapperDispatchToProps
)(RouterWrapperWithAuth0)
<|endoftext|>


<commit_before>// Testacular configuration
// Generated on Wed Oct 10 2012 14:10:32 GMT-0700 (PDT)

// base path, that will be used to resolve files and exclude
basePath = '..';

// list of files / patterns to load in the browser
files = [
  MOCHA,
  MOCHA_ADAPTER,
  'node_modules/expect.js/expect.js',
  'src/client/**/*.js'
];

// list of files to exclude
exclude = [

];

// test results reporter to use
// possible values: dots || progress
reporter = 'progress';

// web server port
port = 8080;

// cli runner port
runnerPort = 9100;

// enable / disable colors in the output (reporters and logs)
colors = true;

// level of logging
// possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
logLevel = LOG_INFO;

// enable / disable watching file and executing tests whenever any file changes
autoWatch = false;

// Start these browsers, currently available:
// - Chrome
// - ChromeCanary
// - Firefox
// - Opera
// - Safari
// - PhantomJS
browsers = [];

// Continuous Integration mode
// if true, it capture browsers, run tests and exit
singleRun = false;
<commit_msg>Fixed Testacular configuration<commit_after>// Testacular configuration
// Generated on Wed Oct 10 2012 14:10:32 GMT-0700 (PDT)

// base path, that will be used to resolve files and exclude
basePath = '../..';

// list of files / patterns to load in the browser
files = [
  MOCHA,
  MOCHA_ADAPTER,
  'node_modules/expect.js/expect.js',
  'src/client/**/*_test.js'
];

// list of files to exclude
exclude = [

];

// test results reporter to use
// possible values: dots || progress
reporter = 'progress';

// web server port
port = 8080;

// cli runner port
runnerPort = 9100;

// enable / disable colors in the output (reporters and logs)
colors = true;

// level of logging
// possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
logLevel = LOG_INFO;

// enable / disable watching file and executing tests whenever any file changes
autoWatch = false;

// Start these browsers, currently available:
// - Chrome
// - ChromeCanary
// - Firefox
// - Opera
// - Safari
// - PhantomJS
browsers = [];

// Continuous Integration mode
// if true, it capture browsers, run tests and exit
singleRun = false;
<|endoftext|>


<commit_before>var model = require('./model');
var error = require('../../../error');

var filter = { _id: 0 };

function getContact(params, callback) {
    console.log('GET /contact');

    var q = model.Q({ uid: 1, black: 0 }, params),
        f = model.F(filter, params);

    model.Contact.find(q, f, function (err, doc) {
        if (err) callback(error.e503);
        else callback(err, doc);
    });
}

function putContact(params, callback) {
    console.log('PUT /contact');

    var q = model.Q({ uid: 1, cid: 1 }, params),
        s = model.S({ alias: 1, black: 1 }, params);

    model.Contact.update(q, s, { upsert: true }, function (err) {
        if (err) callback(error.e503);
        else callback();
    });
}

function delContact(params, callback) {
    console.log('DELETE /contact');

    var q = model.Q({ uid: 1, cid: 1 }, params);
    model.Contact.remove(q, function (err) {
        if (err) callback(error.e503);
        else callback();
    });
}

module.exports = {

    get: getContact,
    put: putContact,
    del: delContact
};<commit_msg>Fix bugs in contact handler<commit_after>var model = require('./model');
var error = require('../../../error');

var filter = { _id: 0 };

function getContact(params, callback) {
    //console.log('GET /contact');

    var q = model.Q({ uid: 1, black: 0 }, params),
        f = model.F(filter, params);

    if (q == null)
        return callback(error.e400);

    model.Contact.find(q, f).lean().exec(function (err, doc) {
        if (err) callback(error.e503);
        else callback(err, doc);
    });
}

function putContact(params, callback) {
    //console.log('PUT /contact');

    var q = model.Q({ uid: 1, cid: 1 }, params),
        s = model.Q({ uid: 1, cid: 1, alias: 0, black: 1 }, params);

    if (q == null || s == null)
        return callback(error.e400);

    model.Contact.update(q, s, { upsert: true, multi: true }, function (err) {
        if (err) callback(error.e503);
        else callback();
    });
}

function delContact(params, callback) {
    //console.log('DELETE /contact');

    var q = model.Q({ uid: 1, cid: 1 }, params);

    if (q == null)
        return callback(error.e400);

    model.Contact.remove(q, function (err) {
        if (err) callback(error.e503);
        else callback();
    });
}

module.exports = {

    get: getContact,
    put: putContact,
    del: delContact
};<|endoftext|>


<commit_before>;(function ($, window, document, undefined) {
    "use strict";

    // Create the defaults once
    var pluginName = "mztxScheduler";

    // The actual plugin constructor
    function Plugin(element /*, options */) {
        this.element = element;

        this.screen_colors = [
            {
                dark: '#010432',
                normal: '#9A9EF6',
                light: '#D9DAF6'
            },
            {
                dark: '#012E2D',
                normal: '#8AF4F0',
                light: '#D3F4F3'
            },
            {
                dark: '#4C3902',
                normal: '#FBDE8D',
                light: '#FBF2D8'
            },
            {
                dark: '#4C1002',
                normal: '#FBA28D',
                light: '#FBDFD8'
            },
            {
                dark: '#354802',
                normal: '#DBFA8D',
                light: '#F0FAD7'
            },
            {
                dark: '#1D0130',
                normal: '#CD91F5',
                light: '#E9D6F5'
            }
        ];
        this.screen_colors_count = this.screen_colors.length;
        this.global_evt_src = 1;

        this.url_get_schedule = $(this.element).data('urlGetSchedule');
        this.url_add_scheduled = $(this.element).data('urlAddScheduled');
        this.url_change_scheduled = $(this.element).data('urlChangeScheduled');
        this.url_delete_scheduled = $(this.element).data('urlDeleteScheduled');

        this._name = pluginName;
        this.init();
    }

    // Avoid Plugin.prototype conflicts
    $.extend(Plugin.prototype, {
        init: function () {
            var today = moment();
            var tmpView = 'agendaWeek';
            var vars = window.location.hash.split("&");
            for (var i = 0; i < vars.length; i++) {
                if (vars[i].match("^#year")) {
                    today.year(vars[i].substring(6));
                }
                if (vars[i].match("^month")) {
                    //tmpMonth = vars[i].substring(6) - 1;
                    today.month(vars[i].substring(6));
                }
                if (vars[i].match("^day")) {
                    today.day(vars[i].substring(4));
                }
                if (vars[i].match("^view")) {
                    tmpView = vars[i].substring(5);
                }
                if (vars[i].match("^selectedScreen")) {
                    this.setSelectedScreen(vars[i].substring(15));
                }
            }

            // @TODO visible screens

            $('.calendar', this.element).fullCalendar({
                header: {
                    left: 'prev,next today',
                    center: 'title',
                    right: 'month,agendaWeek,agendaDay'
                },
                height: 'auto',
                locale: 'de',
                defaultView: tmpView,
                defaultDate: today,
                viewRender: $.proxy(function (view) {
                    this.setUrl(view);
                }, this),
                navLinks: true,     // can click day/week names to navigate views
                editable: true,
                eventLimit: true,   // allow "more" link when too many events
                nowIndicator: true,
                allDaySlot: false,
                snapDuration: '00:05:00',
                events: [],
                selectable: true,
                selectHelper: true,
                select: $.proxy(this.placePresentationBySelection, this),
                eventResize: $.proxy(this.resizePresentation, this),
                eventRender: $.proxy(function(event, element) {
                    if (event.className[0] == 'fc-helper') {
                        var col = this.screen_colors[$(this.getSelectedScreen()).data('color-set')];
                        $(element).css('background-color', col.dark);
                        $(element).css('color', col.light);
                    }
                    if (!event.presentation) {
                        return;
                    }

                    element.append('
' + event.presentation.title + '
');
                    element.find('.fc-content').append("

");
                    element.find('.fc-content .event-delete').on('click', $.proxy(function(e) {
                        this.deletePresentation(event);
                        e.stopPropagation();
                    }, this));
                }, this),
                eventDrop: $.proxy(this.movePresentation, this)
            });

            $('.sched-screen-list .item', this.element).on("click", $.proxy(function (e) {
                $('.sched-screen-list .item', this.element).removeClass('selected');
                $(e.currentTarget).addClass('selected');
                this.setUrl($('.calendar', this.element).fullCalendar('getView'));
            }, this));
            $('.sched-screen-list .item .selector', this.element).on("click", $.proxy(function (e) {
                var button = $(e.currentTarget).parent();
                button.toggleClass('visible');
                e.stopPropagation();

                if (button.hasClass('visible')) {
                    this.addEventSource(button.get(0), button.data('event-src'));
                    this.setScreenColor(button.get(0));
                } else {
                    $('.calendar', this.element).fullCalendar('removeEventSource', button.data('event-src'));
                    this.setScreenColor(button.get(0));
                }
            }, this));

            $('.sched-screen-list .item:first', this.element).trigger('click');

            var global_i = 0;
            $('.sched-screen-list .item', this.element).each($.proxy(function (k, o) {
                var i = global_i % this.screen_colors_count;
                $(o).data('color-set', i);
                this.setScreenColor(o);
                global_i++;
            }, this));

            $('.sched-screen-list .visible', this.element).each($.proxy(function(key, o) {
                $(o).data('event-src', this.global_evt_src);
                this.addEventSource(o, this.global_evt_src);
                this.global_evt_src++;
            }, this));

            this.initCreateDialog();
        },
        setUrl: function (view) {
            var moment = view.start;
            if (moment) {
                window.location.hash =
                    'year=' + moment.format('YYYY') +
                    '&month=' + moment.format('M') +
                    '&day=' + moment.format('DD') +
                    '&view=' + view.name +
                    '&selectedScreen=' + $(this.getSelectedScreen()).data('guid');
            }
        },
        addEventSource: function (screen, id) {
            var colset = this.screen_colors[$(screen).data('color-set')];
            var bg_col = colset.dark;
            var fg_col = colset.light;
            $('.calendar', this.element).fullCalendar('addEventSource',
                {
                    id: id,
                    url: this.url_get_schedule,
                    type: 'post',
                    data: {
                        screen:  $(screen).data('guid')
                    },
                    error: function () {
                        // @TODO use notify
                        alert('there was an error while fetching events!');
                    },
                    color: bg_col,
                    textColor: fg_col
                }
            );
        },
        saveChanged: function (event, revertFunc) {
            // @TODO Read messages from translations
            window.ajaxLoadShow();
            $.ajax({
                url: this.url_change_scheduled,
                method: 'post',
                data: {
                    'id':           event.id,
                    'screen':       event.screen,
                    'presentation': event.title,
                    'start':        event.start.format('YYYY-MM-DD HH:mm:ss'),
                    'end':          event.end.format('YYYY-MM-DD HH:mm:ss')
                }
            }).fail(function() {
                if (revertFunc != undefined) {
                    revertFunc();
                }
                $.notify("Leider ist beim Ändern des Eintrags ein Fehler aufgetreten.", "error");
            }).done(function() {
                $('.calendar', this.element).fullCalendar('refetchEventSources', event.source);
                $.notify("Änderung gespeichert.", "success");
            }).always(function () {
                window.ajaxLoadHide();
            });
        },
        getSelectedScreen: function () {
            return $('.sched-screen-list .selected', this.element).get(0);
        },
        setSelectedScreen: function (guid) {
            $('.sched-screen-list [data^="' + guid + '"]', this.element).removeClass('selected');
            $('.sched-screen-list [data="' + guid + '"]', this.element).addClass('selected');
            return this;
        },
        setScreenColor: function (screen) {
            var col = this.screen_colors[$(screen).data('color-set')];
            if ($(screen).hasClass('visible')) {
                $(screen).css('background-color', col.dark);
                $(screen).css('color', col.light);
                $(screen).children('.selector').css('background-color', col.normal);
            } else {
                $(screen).css('background-color', col.light);
                $(screen).css('color', col.normal);
            }
        },
        placePresentationBySelection: $.proxy(function (start, end) {
            this.showCreateDialog(
                start,
                end,
                $.proxy(function () {
                    $('.calendar', this.element).fullCalendar('refetchEvents');
                }, this)
            );
        }, this),
        resizePresentation: function(event, delta, revertFunc) {
            this.saveChanged(event, revertFunc);
        },
        movePresentation: function(event, delta, revertFunc) {
            this.saveChanged(event, revertFunc);
        },
        deletePresentation: function (event) {
            // @TODO Move this to dialog or anything other translatable
            var r = confirm("Soll die Präsentation  \"" + event.presentation.title + "\" (" +
                event.start.format('DD.MM.YYYY HH:mm') + ") wirklich entfernt werden?");

            if (r !== true) {
                return;
            }

            /** global: url_delete_scheduled */
            window.ajaxLoadShow();
            $.ajax({
                url: this.url_delete_scheduled,
                method: 'POST',
                data: {
                    'id':  event.id
                }
            }).done(function() {
                $('.calendar', this.element).fullCalendar('refetchEventSources', event.source);
                $.notify("Präsentation entfernt.", "success");
            }).always(function () {
                window.ajaxLoadHide();
            });
        },

        showCreateDialog: function (start, stop, success) {
            $('#scheduler-diag-place').find('#date-start').val(start.format('DD.MM.YYYY HH:mm'));
            $('#scheduler-diag-place').find('#date-end').val(stop.format('DD.MM.YYYY HH:mm'));

            $('#scheduler-diag-place').data('successCallback', success);
            $('#scheduler-diag-place').modal('show');
        },

        initCreateDialog: function () {
            var that = this;
            $('#scheduler-diag-place').modal({
                closable: true,
                onApprove: function () {
                    var form = $('form', this);

                    window.ajaxLoadShow();
                    $.ajax({
                        type: 'post',
                        url: form.attr('action'),
                        data: form.serialize()
                    })
                    .fail(function () {
                        $.notify("Beim Eintragen ist leider ein Fehler aufgetreten.", "error");
                    })
                    .done(function () {
                        $.notify("Die Präsentation wurde eingetragen.", "success");
                        var cb = $('#scheduler-diag-place').data('successCallback');
                        cb();
                    })
                    .always(function () {
                        window.ajaxLoadHide();
                    });
                },
                onHide: function () {
                    $('.calendar', that.element).fullCalendar('unselect');
                },
                onHidden: function () {
                    $('form', this).trigger('reset');
                },
                onShow: function () {
                    $('#inp-screen', this).empty();
                    $('#inp-screen', this).append($('#screen-prototype').clone());
                    $('#inp-pres', this).empty();
                    $('#inp-pres', this).append($('#presentation-prototype').clone());
                    $('#inp-screen', this).children('select').val($(that.getSelectedScreen()).data('guid'));

                    $.datetimepicker.setLocale('de');
                    $('.date-pick', this).datetimepicker({
                        format: 'd.m.Y H:i'
                    });
                }
            });
        }
    });

    // A really lightweight plugin wrapper around the constructor, preventing against multiple instantiations
    $.fn[pluginName] = function(options) {
        return this.each(function() {
            if (!$.data(this, "plugin_" + pluginName)) {
                $.data(this, "plugin_" + pluginName, new Plugin(this, options));
            }
        });
    };

    $('[data-scheduler="true"]').mztxScheduler();
})(jQuery, window, document);
<commit_msg>fix eslint problem<commit_after>;(function ($, window, document, undefined) {
    "use strict";

    // Create the defaults once
    var pluginName = "mztxScheduler";

    // The actual plugin constructor
    function Plugin(element /*, options */) {
        this.element = element;

        this.screen_colors = [
            {
                dark: '#010432',
                normal: '#9A9EF6',
                light: '#D9DAF6'
            },
            {
                dark: '#012E2D',
                normal: '#8AF4F0',
                light: '#D3F4F3'
            },
            {
                dark: '#4C3902',
                normal: '#FBDE8D',
                light: '#FBF2D8'
            },
            {
                dark: '#4C1002',
                normal: '#FBA28D',
                light: '#FBDFD8'
            },
            {
                dark: '#354802',
                normal: '#DBFA8D',
                light: '#F0FAD7'
            },
            {
                dark: '#1D0130',
                normal: '#CD91F5',
                light: '#E9D6F5'
            }
        ];
        this.screen_colors_count = this.screen_colors.length;
        this.global_evt_src = 1;

        this.url_get_schedule = $(this.element).data('urlGetSchedule');
        this.url_add_scheduled = $(this.element).data('urlAddScheduled');
        this.url_change_scheduled = $(this.element).data('urlChangeScheduled');
        this.url_delete_scheduled = $(this.element).data('urlDeleteScheduled');

        this._name = pluginName;
        this.init();
    }

    // Avoid Plugin.prototype conflicts
    $.extend(Plugin.prototype, {
        init: function () {
            var today = moment();
            var tmpView = 'agendaWeek';
            var vars = window.location.hash.split("&");
            for (var url_i = 0; url_i < vars.length; url_i++) {
                if (vars[url_i].match("^#year")) {
                    today.year(vars[url_i].substring(6));
                }
                if (vars[url_i].match("^month")) {
                    today.month(vars[url_i].substring(6));
                }
                if (vars[url_i].match("^day")) {
                    today.day(vars[url_i].substring(4));
                }
                if (vars[url_i].match("^view")) {
                    tmpView = vars[url_i].substring(5);
                }
                if (vars[url_i].match("^selectedScreen")) {
                    this.setSelectedScreen(vars[url_i].substring(15));
                }
            }

            // @TODO visible screens

            $('.calendar', this.element).fullCalendar({
                header: {
                    left: 'prev,next today',
                    center: 'title',
                    right: 'month,agendaWeek,agendaDay'
                },
                height: 'auto',
                locale: 'de',
                defaultView: tmpView,
                defaultDate: today,
                viewRender: $.proxy(function (view) {
                    this.setUrl(view);
                }, this),
                navLinks: true,     // can click day/week names to navigate views
                editable: true,
                eventLimit: true,   // allow "more" link when too many events
                nowIndicator: true,
                allDaySlot: false,
                snapDuration: '00:05:00',
                events: [],
                selectable: true,
                selectHelper: true,
                select: $.proxy(this.placePresentationBySelection, this),
                eventResize: $.proxy(this.resizePresentation, this),
                eventRender: $.proxy(function(event, element) {
                    if (event.className[0] == 'fc-helper') {
                        var col = this.screen_colors[$(this.getSelectedScreen()).data('color-set')];
                        $(element).css('background-color', col.dark);
                        $(element).css('color', col.light);
                    }
                    if (!event.presentation) {
                        return;
                    }

                    element.append('
' + event.presentation.title + '
');
                    element.find('.fc-content').append("

");
                    element.find('.fc-content .event-delete').on('click', $.proxy(function(e) {
                        this.deletePresentation(event);
                        e.stopPropagation();
                    }, this));
                }, this),
                eventDrop: $.proxy(this.movePresentation, this)
            });

            $('.sched-screen-list .item', this.element).on("click", $.proxy(function (e) {
                $('.sched-screen-list .item', this.element).removeClass('selected');
                $(e.currentTarget).addClass('selected');
                this.setUrl($('.calendar', this.element).fullCalendar('getView'));
            }, this));
            $('.sched-screen-list .item .selector', this.element).on("click", $.proxy(function (e) {
                var button = $(e.currentTarget).parent();
                button.toggleClass('visible');
                e.stopPropagation();

                if (button.hasClass('visible')) {
                    this.addEventSource(button.get(0), button.data('event-src'));
                    this.setScreenColor(button.get(0));
                } else {
                    $('.calendar', this.element).fullCalendar('removeEventSource', button.data('event-src'));
                    this.setScreenColor(button.get(0));
                }
            }, this));

            $('.sched-screen-list .item:first', this.element).trigger('click');

            var global_i = 0;
            $('.sched-screen-list .item', this.element).each($.proxy(function (k, o) {
                var i = global_i % this.screen_colors_count;
                $(o).data('color-set', i);
                this.setScreenColor(o);
                global_i++;
            }, this));

            $('.sched-screen-list .visible', this.element).each($.proxy(function(key, o) {
                $(o).data('event-src', this.global_evt_src);
                this.addEventSource(o, this.global_evt_src);
                this.global_evt_src++;
            }, this));

            this.initCreateDialog();
        },
        setUrl: function (view) {
            var moment = view.start;
            if (moment) {
                window.location.hash =
                    'year=' + moment.format('YYYY') +
                    '&month=' + moment.format('M') +
                    '&day=' + moment.format('DD') +
                    '&view=' + view.name +
                    '&selectedScreen=' + $(this.getSelectedScreen()).data('guid');
            }
        },
        addEventSource: function (screen, id) {
            var colset = this.screen_colors[$(screen).data('color-set')];
            var bg_col = colset.dark;
            var fg_col = colset.light;
            $('.calendar', this.element).fullCalendar('addEventSource',
                {
                    id: id,
                    url: this.url_get_schedule,
                    type: 'post',
                    data: {
                        screen:  $(screen).data('guid')
                    },
                    error: function () {
                        // @TODO use notify
                        alert('there was an error while fetching events!');
                    },
                    color: bg_col,
                    textColor: fg_col
                }
            );
        },
        saveChanged: function (event, revertFunc) {
            // @TODO Read messages from translations
            window.ajaxLoadShow();
            $.ajax({
                url: this.url_change_scheduled,
                method: 'post',
                data: {
                    'id':           event.id,
                    'screen':       event.screen,
                    'presentation': event.title,
                    'start':        event.start.format('YYYY-MM-DD HH:mm:ss'),
                    'end':          event.end.format('YYYY-MM-DD HH:mm:ss')
                }
            }).fail(function() {
                if (revertFunc != undefined) {
                    revertFunc();
                }
                $.notify("Leider ist beim Ändern des Eintrags ein Fehler aufgetreten.", "error");
            }).done(function() {
                $('.calendar', this.element).fullCalendar('refetchEventSources', event.source);
                $.notify("Änderung gespeichert.", "success");
            }).always(function () {
                window.ajaxLoadHide();
            });
        },
        getSelectedScreen: function () {
            return $('.sched-screen-list .selected', this.element).get(0);
        },
        setSelectedScreen: function (guid) {
            $('.sched-screen-list [data^="' + guid + '"]', this.element).removeClass('selected');
            $('.sched-screen-list [data="' + guid + '"]', this.element).addClass('selected');
            return this;
        },
        setScreenColor: function (screen) {
            var col = this.screen_colors[$(screen).data('color-set')];
            if ($(screen).hasClass('visible')) {
                $(screen).css('background-color', col.dark);
                $(screen).css('color', col.light);
                $(screen).children('.selector').css('background-color', col.normal);
            } else {
                $(screen).css('background-color', col.light);
                $(screen).css('color', col.normal);
            }
        },
        placePresentationBySelection: $.proxy(function (start, end) {
            this.showCreateDialog(
                start,
                end,
                $.proxy(function () {
                    $('.calendar', this.element).fullCalendar('refetchEvents');
                }, this)
            );
        }, this),
        resizePresentation: function(event, delta, revertFunc) {
            this.saveChanged(event, revertFunc);
        },
        movePresentation: function(event, delta, revertFunc) {
            this.saveChanged(event, revertFunc);
        },
        deletePresentation: function (event) {
            // @TODO Move this to dialog or anything other translatable
            var r = confirm("Soll die Präsentation  \"" + event.presentation.title + "\" (" +
                event.start.format('DD.MM.YYYY HH:mm') + ") wirklich entfernt werden?");

            if (r !== true) {
                return;
            }

            /** global: url_delete_scheduled */
            window.ajaxLoadShow();
            $.ajax({
                url: this.url_delete_scheduled,
                method: 'POST',
                data: {
                    'id':  event.id
                }
            }).done(function() {
                $('.calendar', this.element).fullCalendar('refetchEventSources', event.source);
                $.notify("Präsentation entfernt.", "success");
            }).always(function () {
                window.ajaxLoadHide();
            });
        },

        showCreateDialog: function (start, stop, success) {
            $('#scheduler-diag-place').find('#date-start').val(start.format('DD.MM.YYYY HH:mm'));
            $('#scheduler-diag-place').find('#date-end').val(stop.format('DD.MM.YYYY HH:mm'));

            $('#scheduler-diag-place').data('successCallback', success);
            $('#scheduler-diag-place').modal('show');
        },

        initCreateDialog: function () {
            var that = this;
            $('#scheduler-diag-place').modal({
                closable: true,
                onApprove: function () {
                    var form = $('form', this);

                    window.ajaxLoadShow();
                    $.ajax({
                        type: 'post',
                        url: form.attr('action'),
                        data: form.serialize()
                    })
                    .fail(function () {
                        $.notify("Beim Eintragen ist leider ein Fehler aufgetreten.", "error");
                    })
                    .done(function () {
                        $.notify("Die Präsentation wurde eingetragen.", "success");
                        var cb = $('#scheduler-diag-place').data('successCallback');
                        cb();
                    })
                    .always(function () {
                        window.ajaxLoadHide();
                    });
                },
                onHide: function () {
                    $('.calendar', that.element).fullCalendar('unselect');
                },
                onHidden: function () {
                    $('form', this).trigger('reset');
                },
                onShow: function () {
                    $('#inp-screen', this).empty();
                    $('#inp-screen', this).append($('#screen-prototype').clone());
                    $('#inp-pres', this).empty();
                    $('#inp-pres', this).append($('#presentation-prototype').clone());
                    $('#inp-screen', this).children('select').val($(that.getSelectedScreen()).data('guid'));

                    $.datetimepicker.setLocale('de');
                    $('.date-pick', this).datetimepicker({
                        format: 'd.m.Y H:i'
                    });
                }
            });
        }
    });

    // A really lightweight plugin wrapper around the constructor, preventing against multiple instantiations
    $.fn[pluginName] = function(options) {
        return this.each(function() {
            if (!$.data(this, "plugin_" + pluginName)) {
                $.data(this, "plugin_" + pluginName, new Plugin(this, options));
            }
        });
    };

    $('[data-scheduler="true"]').mztxScheduler();
})(jQuery, window, document);
<|endoftext|>