Merge branch '5.3'
authorMatthias Schmidt <gravatronics@live.com>
Tue, 3 Nov 2020 14:52:48 +0000 (15:52 +0100)
committerMatthias Schmidt <gravatronics@live.com>
Tue, 3 Nov 2020 14:52:48 +0000 (15:52 +0100)
1  2 
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Poll/Editor.js
wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Poll/Editor.js
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml

index a919025776b4f162a9878450710454df100f573c,8efcd60b87e44ce95346f7a776e8fd3a899d277a..5a866f21dde997b90ce2407fa649fa34458506a1
   * @since     5.2
   */
  define([
 -      'Core',
 -      'Dom/Util',
 -      'EventHandler',
 -      'EventKey',
 -      'Language',
 -      'WoltLabSuite/Core/Date/Picker',
 -      'WoltLabSuite/Core/Ui/Sortable/List'
 -], function(
 -      Core,
 -      DomUtil,
 -      EventHandler,
 -      EventKey,
 -      Language,
 -      DatePicker,
 -      UiSortableList
 -) {
 -      "use strict";
 -      
 -      function UiPollEditor(containerId, pollOptions, wysiwygId, options) {
 -              this.init(containerId, pollOptions, wysiwygId, options);
 -      }
 -      UiPollEditor.prototype = {
 -              /**
 -               * Initializes the poll editor.
 -               * 
 -               * @param       {string}        containerId     id of the poll options container
 -               * @param       {object[]}      pollOptions     existing poll options
 -               * @param       {string}        wysiwygId       id of the related wysiwyg editor
 -               * @param       {object}        options         additional poll options
 -               */
 -              init: function(containerId, pollOptions, wysiwygId, options) {
 -                      this._container = elById(containerId);
 -                      if (this._container === null) {
 -                              throw new Error("Unknown poll editor container with id '" + containerId + "'.");
 -                      }
 -                      
 -                      this._wysiwygId = wysiwygId;
 -                      if (wysiwygId !== '' && elById(wysiwygId) === null) {
 -                              throw new Error("Unknown wysiwyg field with id '" + wysiwygId + "'.");
 -                      }
 -                      
 -                      this.questionField = elById(this._wysiwygId + 'Poll_question');
 -                      
 -                      var optionLists = elByClass('sortableList', this._container);
 -                      if (optionLists.length === 0) {
 -                              throw new Error("Cannot find poll options list for container with id '" + containerId + "'.");
 -                      }
 -                      this.optionList = optionLists[0];
 -                      
 -                      this.endTimeField = elById(this._wysiwygId + 'Poll_endTime');
 -                      this.maxVotesField = elById(this._wysiwygId + 'Poll_maxVotes');
 -                      this.isChangeableYesField = elById(this._wysiwygId + 'Poll_isChangeable');
 -                      this.isChangeableNoField = elById(this._wysiwygId + 'Poll_isChangeable_no');
 -                      this.isPublicYesField = elById(this._wysiwygId + 'Poll_isPublic');
 -                      this.isPublicNoField = elById(this._wysiwygId + 'Poll_isPublic_no');
 -                      this.resultsRequireVoteYesField = elById(this._wysiwygId + 'Poll_resultsRequireVote');
 -                      this.resultsRequireVoteNoField = elById(this._wysiwygId + 'Poll_resultsRequireVote_no');
 -                      this.sortByVotesYesField = elById(this._wysiwygId + 'Poll_sortByVotes');
 -                      this.sortByVotesNoField = elById(this._wysiwygId + 'Poll_sortByVotes_no');
 -                      
 -                      this._optionCount = 0;
 -                      this._options = Core.extend({
 -                              isAjax: false,
 -                              maxOptions: 20
 -                      }, options);
 -                      
 -                      this._createOptionList(pollOptions || []);
 -                      
 -                      new UiSortableList({
 -                              containerId: containerId,
 -                              options: {
 -                                      toleranceElement: '> div'
 -                              }
 -                      });
 -                      
 -                      if (this._options.isAjax) {
 -                              var events = ['handleError', 'reset', 'submit', 'validate'];
 -                              for (var i = 0, length = events.length; i < length; i++) {
 -                                      var event = events[i];
 -                                      
 -                                      EventHandler.add(
 -                                              'com.woltlab.wcf.redactor2',
 -                                              event + '_' + this._wysiwygId,
 -                                              this['_' + event].bind(this)
 -                                      );
 -                              }
 -                      }
 -                      else {
 -                              var form = this._container.closest('form');
 -                              if (form === null) {
 -                                      throw new Error("Cannot find form for container with id '" + containerId + "'.");
 -                              }
 -                              
 -                              form.addEventListener('submit', this._submit.bind(this));
 -                      }
 -              },
 -              
 -              /**
 -               * Adds an option based on below the option for which the `Add Option` button has
 -               * been clicked.
 -               * 
 -               * @param       {Event}         event           icon click event
 -               */
 -              _addOption: function(event) {
 -                      event.preventDefault();
 -                      
 -                      if (this._optionCount === this._options.maxOptions) {
 -                              return false;
 -                      }
 -                      
 -                      this._createOption(
 -                              undefined,
 -                              undefined,
 -                              event.currentTarget.closest('li')
 -                      );
 -              },
 -              
 -              /**
 -               * Creates a new option based on the given data or an empty option if no option data
 -               * is given.
 -               * 
 -               * @param       {string}        optionValue     value of the option
 -               * @param       {integer}       optionId        id of the option
 -               * @param       {Element?}      insertAfter     optional element after which the new option is added
 -               * @private
 -               */
 -              _createOption: function(optionValue, optionId, insertAfter) {
 -                      optionValue = optionValue || '';
 -                      optionId = ~~optionId || 0;
 -                      
 -                      var listItem = elCreate('LI');
 -                      listItem.className = 'sortableNode';
 -                      elData(listItem, 'option-id', optionId);
 -                      
 -                      if (insertAfter) {
 -                              DomUtil.insertAfter(listItem, insertAfter);
 -                      }
 -                      else {
 -                              this.optionList.appendChild(listItem);
 -                      }
 -                      
 -                      var pollOptionInput = elCreate('div');
 -                      pollOptionInput.className = 'pollOptionInput';
 -                      listItem.appendChild(pollOptionInput);
 -                      
 -                      var sortHandle = elCreate('span');
 -                      sortHandle.className = 'icon icon16 fa-arrows sortableNodeHandle';
 -                      pollOptionInput.appendChild(sortHandle);
 -                      
 -                      // buttons
 -                      var addButton = elCreate('a');
 -                      elAttr(addButton, 'role', 'button');
 -                      elAttr(addButton, 'href', '#');
 -                      addButton.className = 'icon icon16 fa-plus jsTooltip jsAddOption pointer';
 -                      elAttr(addButton, 'title', Language.get('wcf.poll.button.addOption'));
 -                      addButton.addEventListener('click', this._addOption.bind(this));
 -                      pollOptionInput.appendChild(addButton);
 -                      
 -                      var deleteButton = elCreate('a');
 -                      elAttr(deleteButton, 'role', 'button');
 -                      elAttr(deleteButton, 'href', '#');
 -                      deleteButton.className = 'icon icon16 fa-times jsTooltip jsDeleteOption pointer';
 -                      elAttr(deleteButton, 'title', Language.get('wcf.poll.button.removeOption'));
 -                      deleteButton.addEventListener('click', this._removeOption.bind(this));
 -                      pollOptionInput.appendChild(deleteButton);
 -                      
 -                      // input field
 -                      var optionInput = elCreate('input');
 -                      elAttr(optionInput, 'type', 'text');
 -                      optionInput.value = optionValue;
 -                      elAttr(optionInput, 'maxlength', 255);
 -                      optionInput.addEventListener('keydown', this._optionInputKeyDown.bind(this));
 -                      optionInput.addEventListener('click', function() {
 -                              // work-around for some weird focus issue on iOS/Android
 -                              if (document.activeElement !== this) {
 -                                      this.focus();
 -                              }
 -                      });
 -                      pollOptionInput.appendChild(optionInput);
 -                      
 -                      if (insertAfter !== null) {
 -                              optionInput.focus();
 -                      }
 -                      
 -                      this._optionCount++;
 -                      if (this._optionCount === this._options.maxOptions) {
 -                              elBySelAll('span.jsAddOption', this.optionList, function(icon) {
 -                                      icon.classList.remove('pointer');
 -                                      icon.classList.add('disabled');
 -                              });
 -                      }
 -              },
 -              
 -              /**
 -               * Adds the given poll option to the option list.
 -               * 
 -               * @param       {object[]}      pollOptions     data of the added options
 -               */
 -              _createOptionList: function(pollOptions) {
 -                      for (var i = 0, length = pollOptions.length; i < length; i++) {
 -                              var option = pollOptions[i];
 -                              this._createOption(option.optionValue, option.optionID);
 -                      }
 -                      
 -                      // add empty option field to add new options
 -                      if (this._optionCount < this._options.maxOptions) {
 -                              this._createOption();
 -                      }
 -              },
 -              
 -              /**
 -               * Handles errors when the data is saved via AJAX.
 -               * 
 -               * @param       {object}        data    request response data
 -               */
 -              _handleError: function (data) {
 -                      switch (data.returnValues.fieldName) {
 -                              case this._wysiwygId + 'Poll_endTime':
 -                              case this._wysiwygId + 'Poll_maxVotes':
 -                                      var fieldName = data.returnValues.fieldName.replace(this._wysiwygId + 'Poll_', '');
 -                                      
 -                                      var small = elCreate('small');
 -                                      small.className = 'innerError';
 -                                      small.innerHTML = Language.get('wcf.poll.' + fieldName + '.error.' + data.returnValues.errorType);
 -                                      
 -                                      var element = elById(data.returnValues.fieldName);
 -                                      var errorParent = element.closest('dd');
 -                                      
 -                                      DomUtil.prepend(small, element.nextSibling);
 -                                      
 -                                      data.cancel = true;
 -                                      break;
 -                      }
 -              },
 -              
 -              /**
 -               * Adds an empty poll option after the current option when clicking enter.
 -               * 
 -               * @param       {Event}         event   key event
 -               */
 -              _optionInputKeyDown: function(event) {
 -                      // ignore every key except for [Enter]
 -                      if (!EventKey.Enter(event)) {
 -                              return;
 -                      }
 -                      
 -                      Core.triggerEvent(elByClass('jsAddOption', event.currentTarget.parentNode)[0], 'click');
 -                      
 -                      event.preventDefault();
 -              },
 -              
 -              /**
 -               * Removes a poll option after clicking on the `Remove Option` button.
 -               * 
 -               * @param       {Event}         event   click event
 -               */
 -              _removeOption: function (event) {
 -                      event.preventDefault();
 -                      
 -                      elRemove(event.currentTarget.closest('li'));
 -                      
 -                      this._optionCount--;
 -                      
 -                      elBySelAll('span.jsAddOption', this.optionList, function(icon) {
 -                              icon.classList.add('pointer');
 -                              icon.classList.remove('disabled');
 -                      });
 -                      
 -                      if (this.optionList.length === 0) {
 -                              this._createOption();
 -                      }
 -              },
 -              
 -              /**
 -               * Resets all poll-related form fields.
 -               */
 -              _reset: function() {
 -                      this.questionField.value = '';
 -                      
 -                      this._optionCount = 0;
 -                      this.optionList.innerHtml = '';
 -                      this._createOption();
 -                      
 -                      DatePicker.clear(this.endTimeField);
 -                      
 -                      this.maxVotesField.value = 1;
 -                      this.isChangeableYesField.checked = false;
 -                      this.isChangeableNoField.checked = true;
 -                      this.isPublicYesField.checked = false;
 -                      this.isPublicNoField.checked = true;
 -                      this.resultsRequireVoteYesField.checked = false;
 -                      this.resultsRequireVoteNoField.checked = true;
 -                      this.sortByVotesYesField.checked = false;
 -                      this.sortByVotesNoField.checked = true;
 -                      
 -                      EventHandler.fire(
 -                              'com.woltlab.wcf.poll.editor',
 -                              'reset',
 -                              {
 -                                      pollEditor: this
 -                              }
 -                      );
 -              },
 -              
 -              /**
 -               * Is called if the form is submitted or before the AJAX request is sent.
 -               * 
 -               * @param       {Event?}        event   form submit event
 -               */
 -              _submit: function(event) {
 -                      if (this._options.isAjax) {
 -                              event.poll = this.getData();
 -                              
 -                              EventHandler.fire(
 -                                      'com.woltlab.wcf.poll.editor',
 -                                      'submit',
 -                                      {
 -                                              event: event,
 -                                              pollEditor: this
 -                                      }
 -                              );
 -                      }
 -                      else {
 -                              var form = this._container.closest('form');
 -                              
 -                              var options = this.getOptions();
 -                              for (var i = 0, length = options.length; i < length; i++) {
 -                                      var input = elCreate('input');
 -                                      elAttr(input, 'type', 'hidden');
 -                                      elAttr(input, 'name', this._wysiwygId + 'Poll_options[' + i + ']');
 -                                      input.value = options[i];
 -                                      form.appendChild(input);
 -                              }
 -                      }
 -              },
 -              
 -              /**
 -               * Is called to validate the poll data.
 -               * 
 -               * @param       {object}        data    event data
 -               */
 -              _validate: function(data) {
 -                      if (this.questionField.value.trim() === '') {
 -                              return;
 -                      }
 -                      
 -                      var nonEmptyOptionCount = 0;
 -                      for (var i = 0, length = this.optionList.children.length; i < length; i++) {
 -                              var optionInput = elBySel('input[type=text]', this.optionList.children[i]);
 -                              if (optionInput.value.trim() !== '') {
 -                                      nonEmptyOptionCount++;
 -                              }
 -                      }
 -                      
 -                      if (nonEmptyOptionCount === 0) {
 -                              data.api.throwError(this._container, Language.get('wcf.global.form.error.empty'));
 -                              data.valid = false;
 -                      }
 -                      else {
 -                              var maxVotes = ~~this.maxVotesField.value;
 -                              
 -                              if (maxVotes && maxVotes > nonEmptyOptionCount) {
 -                                      data.api.throwError(this.maxVotesField.parentNode, Language.get('wcf.poll.maxVotes.error.invalid'));
 -                                      data.valid = false;
 -                              }
 -                              else {
 -                                      EventHandler.fire(
 -                                              'com.woltlab.wcf.poll.editor',
 -                                              'validate',
 -                                              {
 -                                                      data: data,
 -                                                      pollEditor: this
 -                                              }
 -                                      );
 -                              }
 -                      }
 -              },
 -              
 -              /**
 -               * Returns all poll data.
 -               * 
 -               * @return      {object}
 -               */
 -              getData: function() {
 -                      var data = {};
 -                      
 -                      data[this.questionField.id] = this.questionField.value;
 -                      data[this._wysiwygId + 'Poll_options'] = this.getOptions();
 -                      data[this.endTimeField.id] = this.endTimeField.value;
 -                      data[this.maxVotesField.id] = this.maxVotesField.value;
 -                      data[this.isChangeableYesField.id] = !!this.isChangeableYesField.checked;
 -                      data[this.isPublicYesField.id] = !!this.isPublicYesField.checked;
 -                      data[this.resultsRequireVoteYesField.id] = !!this.resultsRequireVoteYesField.checked;
 -                      data[this.sortByVotesYesField.id] = !!this.sortByVotesYesField.checked;
 -                      
 -                      return data;
 -              },
 -              
 -              /**
 -               * Returns all entered poll options.
 -               * 
 -               * @return      {string[]}
 -               */
 -              getOptions: function() {
 -                      var options = [];
 -                      for (var i = 0, length = this.optionList.children.length; i < length; i++) {
 -                              var listItem = this.optionList.children[i];
 -                              var optionValue = elBySel('input[type=text]', listItem).value.trim();
 -                              
 -                              if (optionValue !== '') {
 -                                      options.push(elData(listItem, 'option-id') + '_' + optionValue);
 -                              }
 -                      }
 -                      
 -                      return options;
 -              }
 -      };
 -      
 -      return UiPollEditor;
 +    'Core',
 +    'Dom/Util',
 +    'EventHandler',
 +    'EventKey',
 +    'Language',
 +    'WoltLabSuite/Core/Date/Picker',
 +    'WoltLabSuite/Core/Ui/Sortable/List'
 +], function (Core, DomUtil, EventHandler, EventKey, Language, DatePicker, UiSortableList) {
 +    "use strict";
 +    function UiPollEditor(containerId, pollOptions, wysiwygId, options) {
 +        this.init(containerId, pollOptions, wysiwygId, options);
 +    }
 +    UiPollEditor.prototype = {
 +        /**
 +         * Initializes the poll editor.
 +         *
 +         * @param     {string}        containerId     id of the poll options container
 +         * @param     {object[]}      pollOptions     existing poll options
 +         * @param     {string}        wysiwygId       id of the related wysiwyg editor
 +         * @param     {object}        options         additional poll options
 +         */
 +        init: function (containerId, pollOptions, wysiwygId, options) {
 +            this._container = elById(containerId);
 +            if (this._container === null) {
 +                throw new Error("Unknown poll editor container with id '" + containerId + "'.");
 +            }
 +            this._wysiwygId = wysiwygId;
 +            if (wysiwygId !== '' && elById(wysiwygId) === null) {
 +                throw new Error("Unknown wysiwyg field with id '" + wysiwygId + "'.");
 +            }
 +            this.questionField = elById(this._wysiwygId + 'Poll_question');
 +            var optionLists = elByClass('sortableList', this._container);
 +            if (optionLists.length === 0) {
 +                throw new Error("Cannot find poll options list for container with id '" + containerId + "'.");
 +            }
 +            this.optionList = optionLists[0];
 +            this.endTimeField = elById(this._wysiwygId + 'Poll_endTime');
 +            this.maxVotesField = elById(this._wysiwygId + 'Poll_maxVotes');
 +            this.isChangeableYesField = elById(this._wysiwygId + 'Poll_isChangeable');
 +            this.isChangeableNoField = elById(this._wysiwygId + 'Poll_isChangeable_no');
 +            this.isPublicYesField = elById(this._wysiwygId + 'Poll_isPublic');
 +            this.isPublicNoField = elById(this._wysiwygId + 'Poll_isPublic_no');
 +            this.resultsRequireVoteYesField = elById(this._wysiwygId + 'Poll_resultsRequireVote');
 +            this.resultsRequireVoteNoField = elById(this._wysiwygId + 'Poll_resultsRequireVote_no');
 +            this.sortByVotesYesField = elById(this._wysiwygId + 'Poll_sortByVotes');
 +            this.sortByVotesNoField = elById(this._wysiwygId + 'Poll_sortByVotes_no');
 +            this._optionCount = 0;
 +            this._options = Core.extend({
 +                isAjax: false,
 +                maxOptions: 20
 +            }, options);
 +            this._createOptionList(pollOptions || []);
 +            new UiSortableList({
 +                containerId: containerId,
 +                options: {
 +                    toleranceElement: '> div'
 +                }
 +            });
 +            if (this._options.isAjax) {
 +                var events = ['handleError', 'reset', 'submit', 'validate'];
 +                for (var i = 0, length = events.length; i < length; i++) {
 +                    var event = events[i];
 +                    EventHandler.add('com.woltlab.wcf.redactor2', event + '_' + this._wysiwygId, this['_' + event].bind(this));
 +                }
 +            }
 +            else {
 +                var form = this._container.closest('form');
 +                if (form === null) {
 +                    throw new Error("Cannot find form for container with id '" + containerId + "'.");
 +                }
 +                form.addEventListener('submit', this._submit.bind(this));
 +            }
 +        },
 +        /**
 +         * Adds an option based on below the option for which the `Add Option` button has
 +         * been clicked.
 +         *
 +         * @param     {Event}         event           icon click event
 +         */
 +        _addOption: function (event) {
 +            event.preventDefault();
 +            if (this._optionCount === this._options.maxOptions) {
 +                return false;
 +            }
 +            this._createOption(undefined, undefined, event.currentTarget.closest('li'));
 +        },
 +        /**
 +         * Creates a new option based on the given data or an empty option if no option data
 +         * is given.
 +         *
 +         * @param     {string}        optionValue     value of the option
 +         * @param     {integer}       optionId        id of the option
 +         * @param     {Element?}      insertAfter     optional element after which the new option is added
 +         * @private
 +         */
 +        _createOption: function (optionValue, optionId, insertAfter) {
 +            optionValue = optionValue || '';
 +            optionId = ~~optionId || 0;
 +            var listItem = elCreate('LI');
 +            listItem.className = 'sortableNode';
 +            elData(listItem, 'option-id', optionId);
 +            if (insertAfter) {
 +                DomUtil.insertAfter(listItem, insertAfter);
 +            }
 +            else {
 +                this.optionList.appendChild(listItem);
 +            }
 +            var pollOptionInput = elCreate('div');
 +            pollOptionInput.className = 'pollOptionInput';
 +            listItem.appendChild(pollOptionInput);
 +            var sortHandle = elCreate('span');
 +            sortHandle.className = 'icon icon16 fa-arrows sortableNodeHandle';
 +            pollOptionInput.appendChild(sortHandle);
 +            // buttons
 +            var addButton = elCreate('a');
 +            elAttr(addButton, 'role', 'button');
 +            elAttr(addButton, 'href', '#');
 +            addButton.className = 'icon icon16 fa-plus jsTooltip jsAddOption pointer';
 +            elAttr(addButton, 'title', Language.get('wcf.poll.button.addOption'));
 +            addButton.addEventListener('click', this._addOption.bind(this));
 +            pollOptionInput.appendChild(addButton);
 +            var deleteButton = elCreate('a');
 +            elAttr(deleteButton, 'role', 'button');
 +            elAttr(deleteButton, 'href', '#');
 +            deleteButton.className = 'icon icon16 fa-times jsTooltip jsDeleteOption pointer';
 +            elAttr(deleteButton, 'title', Language.get('wcf.poll.button.removeOption'));
 +            deleteButton.addEventListener('click', this._removeOption.bind(this));
 +            pollOptionInput.appendChild(deleteButton);
 +            // input field
 +            var optionInput = elCreate('input');
 +            elAttr(optionInput, 'type', 'text');
 +            optionInput.value = optionValue;
 +            elAttr(optionInput, 'maxlength', 255);
 +            optionInput.addEventListener('keydown', this._optionInputKeyDown.bind(this));
 +            optionInput.addEventListener('click', function () {
 +                // work-around for some weird focus issue on iOS/Android
 +                if (document.activeElement !== this) {
 +                    this.focus();
 +                }
 +            });
 +            pollOptionInput.appendChild(optionInput);
 +            if (insertAfter !== null) {
 +                optionInput.focus();
 +            }
 +            this._optionCount++;
 +            if (this._optionCount === this._options.maxOptions) {
 +                elBySelAll('span.jsAddOption', this.optionList, function (icon) {
 +                    icon.classList.remove('pointer');
 +                    icon.classList.add('disabled');
 +                });
 +            }
 +        },
 +        /**
 +         * Adds the given poll option to the option list.
 +         *
 +         * @param     {object[]}      pollOptions     data of the added options
 +         */
 +        _createOptionList: function (pollOptions) {
 +            for (var i = 0, length = pollOptions.length; i < length; i++) {
 +                var option = pollOptions[i];
 +                this._createOption(option.optionValue, option.optionID);
 +            }
 +            // add empty option field to add new options
 +            if (this._optionCount < this._options.maxOptions) {
 +                this._createOption();
 +            }
 +        },
 +        /**
 +         * Handles errors when the data is saved via AJAX.
 +         *
 +         * @param     {object}        data    request response data
 +         */
 +        _handleError: function (data) {
 +            switch (data.returnValues.fieldName) {
 +                case this._wysiwygId + 'Poll_endTime':
 +                case this._wysiwygId + 'Poll_maxVotes':
 +                    var fieldName = data.returnValues.fieldName.replace(this._wysiwygId + 'Poll_', '');
 +                    var small = elCreate('small');
 +                    small.className = 'innerError';
 +                    small.innerHTML = Language.get('wcf.poll.' + fieldName + '.error.' + data.returnValues.errorType);
 +                    var element = elById(data.returnValues.fieldName);
 +                    var errorParent = element.closest('dd');
 +                    DomUtil.prepend(small, element.nextSibling);
 +                    data.cancel = true;
 +                    break;
 +            }
 +        },
 +        /**
 +         * Adds an empty poll option after the current option when clicking enter.
 +         *
 +         * @param     {Event}         event   key event
 +         */
 +        _optionInputKeyDown: function (event) {
 +            // ignore every key except for [Enter]
 +            if (!EventKey.Enter(event)) {
 +                return;
 +            }
 +            Core.triggerEvent(elByClass('jsAddOption', event.currentTarget.parentNode)[0], 'click');
 +            event.preventDefault();
 +        },
 +        /**
 +         * Removes a poll option after clicking on the `Remove Option` button.
 +         *
 +         * @param     {Event}         event   click event
 +         */
 +        _removeOption: function (event) {
 +            event.preventDefault();
 +            elRemove(event.currentTarget.closest('li'));
 +            this._optionCount--;
 +            elBySelAll('span.jsAddOption', this.optionList, function (icon) {
 +                icon.classList.add('pointer');
 +                icon.classList.remove('disabled');
 +            });
 +            if (this.optionList.length === 0) {
 +                this._createOption();
 +            }
 +        },
 +        /**
 +         * Resets all poll-related form fields.
 +         */
 +        _reset: function () {
 +            this.questionField.value = '';
 +            this._optionCount = 0;
 +            this.optionList.innerHtml = '';
 +            this._createOption();
 +            DatePicker.clear(this.endTimeField);
 +            this.maxVotesField.value = 1;
 +            this.isChangeableYesField.checked = false;
 +            this.isChangeableNoField.checked = true;
-             this.isPublicYesField = false;
-             this.isPublicNoField = true;
-             this.resultsRequireVoteYesField = false;
-             this.resultsRequireVoteNoField = true;
-             this.sortByVotesYesField = false;
-             this.sortByVotesNoField = true;
++            this.isPublicYesField.checked = false;
++            this.isPublicNoField.checked = true;
++            this.resultsRequireVoteYesField.checked = false;
++            this.resultsRequireVoteNoField.checked = true;
++            this.sortByVotesYesField.checked = false;
++            this.sortByVotesNoField.checked = true;
 +            EventHandler.fire('com.woltlab.wcf.poll.editor', 'reset', {
 +                pollEditor: this
 +            });
 +        },
 +        /**
 +         * Is called if the form is submitted or before the AJAX request is sent.
 +         *
 +         * @param     {Event?}        event   form submit event
 +         */
 +        _submit: function (event) {
 +            if (this._options.isAjax) {
 +                event.poll = this.getData();
 +                EventHandler.fire('com.woltlab.wcf.poll.editor', 'submit', {
 +                    event: event,
 +                    pollEditor: this
 +                });
 +            }
 +            else {
 +                var form = this._container.closest('form');
 +                var options = this.getOptions();
 +                for (var i = 0, length = options.length; i < length; i++) {
 +                    var input = elCreate('input');
 +                    elAttr(input, 'type', 'hidden');
 +                    elAttr(input, 'name', this._wysiwygId + 'Poll_options[' + i + ']');
 +                    input.value = options[i];
 +                    form.appendChild(input);
 +                }
 +            }
 +        },
 +        /**
 +         * Is called to validate the poll data.
 +         *
 +         * @param     {object}        data    event data
 +         */
 +        _validate: function (data) {
 +            if (this.questionField.value.trim() === '') {
 +                return;
 +            }
 +            var nonEmptyOptionCount = 0;
 +            for (var i = 0, length = this.optionList.children.length; i < length; i++) {
 +                var optionInput = elBySel('input[type=text]', this.optionList.children[i]);
 +                if (optionInput.value.trim() !== '') {
 +                    nonEmptyOptionCount++;
 +                }
 +            }
 +            if (nonEmptyOptionCount === 0) {
 +                data.api.throwError(this._container, Language.get('wcf.global.form.error.empty'));
 +                data.valid = false;
 +            }
 +            else {
 +                var maxVotes = ~~this.maxVotesField.value;
 +                if (maxVotes && maxVotes > nonEmptyOptionCount) {
 +                    data.api.throwError(this.maxVotesField.parentNode, Language.get('wcf.poll.maxVotes.error.invalid'));
 +                    data.valid = false;
 +                }
 +                else {
 +                    EventHandler.fire('com.woltlab.wcf.poll.editor', 'validate', {
 +                        data: data,
 +                        pollEditor: this
 +                    });
 +                }
 +            }
 +        },
 +        /**
 +         * Returns all poll data.
 +         *
 +         * @return      {object}
 +         */
 +        getData: function () {
 +            var data = {};
 +            data[this.questionField.id] = this.questionField.value;
 +            data[this._wysiwygId + 'Poll_options'] = this.getOptions();
 +            data[this.endTimeField.id] = this.endTimeField.value;
 +            data[this.maxVotesField.id] = this.maxVotesField.value;
 +            data[this.isChangeableYesField.id] = !!this.isChangeableYesField.checked;
 +            data[this.isPublicYesField.id] = !!this.isPublicYesField.checked;
 +            data[this.resultsRequireVoteYesField.id] = !!this.resultsRequireVoteYesField.checked;
 +            data[this.sortByVotesYesField.id] = !!this.sortByVotesYesField.checked;
 +            return data;
 +        },
 +        /**
 +         * Returns all entered poll options.
 +         *
 +         * @return      {string[]}
 +         */
 +        getOptions: function () {
 +            var options = [];
 +            for (var i = 0, length = this.optionList.children.length; i < length; i++) {
 +                var listItem = this.optionList.children[i];
 +                var optionValue = elBySel('input[type=text]', listItem).value.trim();
 +                if (optionValue !== '') {
 +                    options.push(elData(listItem, 'option-id') + '_' + optionValue);
 +                }
 +            }
 +            return options;
 +        }
 +    };
 +    return UiPollEditor;
  });
index 2aa13c887bf45f05b496373f25d6f44f41617516,0000000000000000000000000000000000000000..47d3c5c57e2073b6e984208ebcee2bf3ec31c500
mode 100644,000000..100644
--- /dev/null
@@@ -1,430 -1,0 +1,430 @@@
-                       this.isPublicYesField = false;
-                       this.isPublicNoField = true;
-                       this.resultsRequireVoteYesField = false;
-                       this.resultsRequireVoteNoField = true;
-                       this.sortByVotesYesField = false;
-                       this.sortByVotesNoField = true;
 +/**
 + * Handles the data to create and edit a poll in a form created via form builder.
 + * 
 + * @author    Alexander Ebert, Matthias Schmidt
 + * @copyright 2001-2020 WoltLab GmbH
 + * @license   GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 + * @module    WoltLabSuite/Core/Ui/Poll/Editor
 + * @since     5.2
 + */
 +define([
 +      'Core',
 +      'Dom/Util',
 +      'EventHandler',
 +      'EventKey',
 +      'Language',
 +      'WoltLabSuite/Core/Date/Picker',
 +      'WoltLabSuite/Core/Ui/Sortable/List'
 +], function(
 +      Core,
 +      DomUtil,
 +      EventHandler,
 +      EventKey,
 +      Language,
 +      DatePicker,
 +      UiSortableList
 +) {
 +      "use strict";
 +      
 +      function UiPollEditor(containerId, pollOptions, wysiwygId, options) {
 +              this.init(containerId, pollOptions, wysiwygId, options);
 +      }
 +      UiPollEditor.prototype = {
 +              /**
 +               * Initializes the poll editor.
 +               * 
 +               * @param       {string}        containerId     id of the poll options container
 +               * @param       {object[]}      pollOptions     existing poll options
 +               * @param       {string}        wysiwygId       id of the related wysiwyg editor
 +               * @param       {object}        options         additional poll options
 +               */
 +              init: function(containerId, pollOptions, wysiwygId, options) {
 +                      this._container = elById(containerId);
 +                      if (this._container === null) {
 +                              throw new Error("Unknown poll editor container with id '" + containerId + "'.");
 +                      }
 +                      
 +                      this._wysiwygId = wysiwygId;
 +                      if (wysiwygId !== '' && elById(wysiwygId) === null) {
 +                              throw new Error("Unknown wysiwyg field with id '" + wysiwygId + "'.");
 +                      }
 +                      
 +                      this.questionField = elById(this._wysiwygId + 'Poll_question');
 +                      
 +                      var optionLists = elByClass('sortableList', this._container);
 +                      if (optionLists.length === 0) {
 +                              throw new Error("Cannot find poll options list for container with id '" + containerId + "'.");
 +                      }
 +                      this.optionList = optionLists[0];
 +                      
 +                      this.endTimeField = elById(this._wysiwygId + 'Poll_endTime');
 +                      this.maxVotesField = elById(this._wysiwygId + 'Poll_maxVotes');
 +                      this.isChangeableYesField = elById(this._wysiwygId + 'Poll_isChangeable');
 +                      this.isChangeableNoField = elById(this._wysiwygId + 'Poll_isChangeable_no');
 +                      this.isPublicYesField = elById(this._wysiwygId + 'Poll_isPublic');
 +                      this.isPublicNoField = elById(this._wysiwygId + 'Poll_isPublic_no');
 +                      this.resultsRequireVoteYesField = elById(this._wysiwygId + 'Poll_resultsRequireVote');
 +                      this.resultsRequireVoteNoField = elById(this._wysiwygId + 'Poll_resultsRequireVote_no');
 +                      this.sortByVotesYesField = elById(this._wysiwygId + 'Poll_sortByVotes');
 +                      this.sortByVotesNoField = elById(this._wysiwygId + 'Poll_sortByVotes_no');
 +                      
 +                      this._optionCount = 0;
 +                      this._options = Core.extend({
 +                              isAjax: false,
 +                              maxOptions: 20
 +                      }, options);
 +                      
 +                      this._createOptionList(pollOptions || []);
 +                      
 +                      new UiSortableList({
 +                              containerId: containerId,
 +                              options: {
 +                                      toleranceElement: '> div'
 +                              }
 +                      });
 +                      
 +                      if (this._options.isAjax) {
 +                              var events = ['handleError', 'reset', 'submit', 'validate'];
 +                              for (var i = 0, length = events.length; i < length; i++) {
 +                                      var event = events[i];
 +                                      
 +                                      EventHandler.add(
 +                                              'com.woltlab.wcf.redactor2',
 +                                              event + '_' + this._wysiwygId,
 +                                              this['_' + event].bind(this)
 +                                      );
 +                              }
 +                      }
 +                      else {
 +                              var form = this._container.closest('form');
 +                              if (form === null) {
 +                                      throw new Error("Cannot find form for container with id '" + containerId + "'.");
 +                              }
 +                              
 +                              form.addEventListener('submit', this._submit.bind(this));
 +                      }
 +              },
 +              
 +              /**
 +               * Adds an option based on below the option for which the `Add Option` button has
 +               * been clicked.
 +               * 
 +               * @param       {Event}         event           icon click event
 +               */
 +              _addOption: function(event) {
 +                      event.preventDefault();
 +                      
 +                      if (this._optionCount === this._options.maxOptions) {
 +                              return false;
 +                      }
 +                      
 +                      this._createOption(
 +                              undefined,
 +                              undefined,
 +                              event.currentTarget.closest('li')
 +                      );
 +              },
 +              
 +              /**
 +               * Creates a new option based on the given data or an empty option if no option data
 +               * is given.
 +               * 
 +               * @param       {string}        optionValue     value of the option
 +               * @param       {integer}       optionId        id of the option
 +               * @param       {Element?}      insertAfter     optional element after which the new option is added
 +               * @private
 +               */
 +              _createOption: function(optionValue, optionId, insertAfter) {
 +                      optionValue = optionValue || '';
 +                      optionId = ~~optionId || 0;
 +                      
 +                      var listItem = elCreate('LI');
 +                      listItem.className = 'sortableNode';
 +                      elData(listItem, 'option-id', optionId);
 +                      
 +                      if (insertAfter) {
 +                              DomUtil.insertAfter(listItem, insertAfter);
 +                      }
 +                      else {
 +                              this.optionList.appendChild(listItem);
 +                      }
 +                      
 +                      var pollOptionInput = elCreate('div');
 +                      pollOptionInput.className = 'pollOptionInput';
 +                      listItem.appendChild(pollOptionInput);
 +                      
 +                      var sortHandle = elCreate('span');
 +                      sortHandle.className = 'icon icon16 fa-arrows sortableNodeHandle';
 +                      pollOptionInput.appendChild(sortHandle);
 +                      
 +                      // buttons
 +                      var addButton = elCreate('a');
 +                      elAttr(addButton, 'role', 'button');
 +                      elAttr(addButton, 'href', '#');
 +                      addButton.className = 'icon icon16 fa-plus jsTooltip jsAddOption pointer';
 +                      elAttr(addButton, 'title', Language.get('wcf.poll.button.addOption'));
 +                      addButton.addEventListener('click', this._addOption.bind(this));
 +                      pollOptionInput.appendChild(addButton);
 +                      
 +                      var deleteButton = elCreate('a');
 +                      elAttr(deleteButton, 'role', 'button');
 +                      elAttr(deleteButton, 'href', '#');
 +                      deleteButton.className = 'icon icon16 fa-times jsTooltip jsDeleteOption pointer';
 +                      elAttr(deleteButton, 'title', Language.get('wcf.poll.button.removeOption'));
 +                      deleteButton.addEventListener('click', this._removeOption.bind(this));
 +                      pollOptionInput.appendChild(deleteButton);
 +                      
 +                      // input field
 +                      var optionInput = elCreate('input');
 +                      elAttr(optionInput, 'type', 'text');
 +                      optionInput.value = optionValue;
 +                      elAttr(optionInput, 'maxlength', 255);
 +                      optionInput.addEventListener('keydown', this._optionInputKeyDown.bind(this));
 +                      optionInput.addEventListener('click', function() {
 +                              // work-around for some weird focus issue on iOS/Android
 +                              if (document.activeElement !== this) {
 +                                      this.focus();
 +                              }
 +                      });
 +                      pollOptionInput.appendChild(optionInput);
 +                      
 +                      if (insertAfter !== null) {
 +                              optionInput.focus();
 +                      }
 +                      
 +                      this._optionCount++;
 +                      if (this._optionCount === this._options.maxOptions) {
 +                              elBySelAll('span.jsAddOption', this.optionList, function(icon) {
 +                                      icon.classList.remove('pointer');
 +                                      icon.classList.add('disabled');
 +                              });
 +                      }
 +              },
 +              
 +              /**
 +               * Adds the given poll option to the option list.
 +               * 
 +               * @param       {object[]}      pollOptions     data of the added options
 +               */
 +              _createOptionList: function(pollOptions) {
 +                      for (var i = 0, length = pollOptions.length; i < length; i++) {
 +                              var option = pollOptions[i];
 +                              this._createOption(option.optionValue, option.optionID);
 +                      }
 +                      
 +                      // add empty option field to add new options
 +                      if (this._optionCount < this._options.maxOptions) {
 +                              this._createOption();
 +                      }
 +              },
 +              
 +              /**
 +               * Handles errors when the data is saved via AJAX.
 +               * 
 +               * @param       {object}        data    request response data
 +               */
 +              _handleError: function (data) {
 +                      switch (data.returnValues.fieldName) {
 +                              case this._wysiwygId + 'Poll_endTime':
 +                              case this._wysiwygId + 'Poll_maxVotes':
 +                                      var fieldName = data.returnValues.fieldName.replace(this._wysiwygId + 'Poll_', '');
 +                                      
 +                                      var small = elCreate('small');
 +                                      small.className = 'innerError';
 +                                      small.innerHTML = Language.get('wcf.poll.' + fieldName + '.error.' + data.returnValues.errorType);
 +                                      
 +                                      var element = elById(data.returnValues.fieldName);
 +                                      var errorParent = element.closest('dd');
 +                                      
 +                                      DomUtil.prepend(small, element.nextSibling);
 +                                      
 +                                      data.cancel = true;
 +                                      break;
 +                      }
 +              },
 +              
 +              /**
 +               * Adds an empty poll option after the current option when clicking enter.
 +               * 
 +               * @param       {Event}         event   key event
 +               */
 +              _optionInputKeyDown: function(event) {
 +                      // ignore every key except for [Enter]
 +                      if (!EventKey.Enter(event)) {
 +                              return;
 +                      }
 +                      
 +                      Core.triggerEvent(elByClass('jsAddOption', event.currentTarget.parentNode)[0], 'click');
 +                      
 +                      event.preventDefault();
 +              },
 +              
 +              /**
 +               * Removes a poll option after clicking on the `Remove Option` button.
 +               * 
 +               * @param       {Event}         event   click event
 +               */
 +              _removeOption: function (event) {
 +                      event.preventDefault();
 +                      
 +                      elRemove(event.currentTarget.closest('li'));
 +                      
 +                      this._optionCount--;
 +                      
 +                      elBySelAll('span.jsAddOption', this.optionList, function(icon) {
 +                              icon.classList.add('pointer');
 +                              icon.classList.remove('disabled');
 +                      });
 +                      
 +                      if (this.optionList.length === 0) {
 +                              this._createOption();
 +                      }
 +              },
 +              
 +              /**
 +               * Resets all poll-related form fields.
 +               */
 +              _reset: function() {
 +                      this.questionField.value = '';
 +                      
 +                      this._optionCount = 0;
 +                      this.optionList.innerHtml = '';
 +                      this._createOption();
 +                      
 +                      DatePicker.clear(this.endTimeField);
 +                      
 +                      this.maxVotesField.value = 1;
 +                      this.isChangeableYesField.checked = false;
 +                      this.isChangeableNoField.checked = true;
++                      this.isPublicYesField.checked = false;
++                      this.isPublicNoField.checked = true;
++                      this.resultsRequireVoteYesField.checked = false;
++                      this.resultsRequireVoteNoField.checked = true;
++                      this.sortByVotesYesField.checked = false;
++                      this.sortByVotesNoField.checked = true;
 +                      
 +                      EventHandler.fire(
 +                              'com.woltlab.wcf.poll.editor',
 +                              'reset',
 +                              {
 +                                      pollEditor: this
 +                              }
 +                      );
 +              },
 +              
 +              /**
 +               * Is called if the form is submitted or before the AJAX request is sent.
 +               * 
 +               * @param       {Event?}        event   form submit event
 +               */
 +              _submit: function(event) {
 +                      if (this._options.isAjax) {
 +                              event.poll = this.getData();
 +                              
 +                              EventHandler.fire(
 +                                      'com.woltlab.wcf.poll.editor',
 +                                      'submit',
 +                                      {
 +                                              event: event,
 +                                              pollEditor: this
 +                                      }
 +                              );
 +                      }
 +                      else {
 +                              var form = this._container.closest('form');
 +                              
 +                              var options = this.getOptions();
 +                              for (var i = 0, length = options.length; i < length; i++) {
 +                                      var input = elCreate('input');
 +                                      elAttr(input, 'type', 'hidden');
 +                                      elAttr(input, 'name', this._wysiwygId + 'Poll_options[' + i + ']');
 +                                      input.value = options[i];
 +                                      form.appendChild(input);
 +                              }
 +                      }
 +              },
 +              
 +              /**
 +               * Is called to validate the poll data.
 +               * 
 +               * @param       {object}        data    event data
 +               */
 +              _validate: function(data) {
 +                      if (this.questionField.value.trim() === '') {
 +                              return;
 +                      }
 +                      
 +                      var nonEmptyOptionCount = 0;
 +                      for (var i = 0, length = this.optionList.children.length; i < length; i++) {
 +                              var optionInput = elBySel('input[type=text]', this.optionList.children[i]);
 +                              if (optionInput.value.trim() !== '') {
 +                                      nonEmptyOptionCount++;
 +                              }
 +                      }
 +                      
 +                      if (nonEmptyOptionCount === 0) {
 +                              data.api.throwError(this._container, Language.get('wcf.global.form.error.empty'));
 +                              data.valid = false;
 +                      }
 +                      else {
 +                              var maxVotes = ~~this.maxVotesField.value;
 +                              
 +                              if (maxVotes && maxVotes > nonEmptyOptionCount) {
 +                                      data.api.throwError(this.maxVotesField.parentNode, Language.get('wcf.poll.maxVotes.error.invalid'));
 +                                      data.valid = false;
 +                              }
 +                              else {
 +                                      EventHandler.fire(
 +                                              'com.woltlab.wcf.poll.editor',
 +                                              'validate',
 +                                              {
 +                                                      data: data,
 +                                                      pollEditor: this
 +                                              }
 +                                      );
 +                              }
 +                      }
 +              },
 +              
 +              /**
 +               * Returns all poll data.
 +               *
 +               * @return      {object}
 +               */
 +              getData: function() {
 +                      var data = {};
 +                      
 +                      data[this.questionField.id] = this.questionField.value;
 +                      data[this._wysiwygId + 'Poll_options'] = this.getOptions();
 +                      data[this.endTimeField.id] = this.endTimeField.value;
 +                      data[this.maxVotesField.id] = this.maxVotesField.value;
 +                      data[this.isChangeableYesField.id] = !!this.isChangeableYesField.checked;
 +                      data[this.isPublicYesField.id] = !!this.isPublicYesField.checked;
 +                      data[this.resultsRequireVoteYesField.id] = !!this.resultsRequireVoteYesField.checked;
 +                      data[this.sortByVotesYesField.id] = !!this.sortByVotesYesField.checked;
 +                      
 +                      return data;
 +              },
 +              
 +              /**
 +               * Returns all entered poll options.
 +               *
 +               * @return      {string[]}
 +               */
 +              getOptions: function() {
 +                      var options = [];
 +                      for (var i = 0, length = this.optionList.children.length; i < length; i++) {
 +                              var listItem = this.optionList.children[i];
 +                              var optionValue = elBySel('input[type=text]', listItem).value.trim();
 +                              
 +                              if (optionValue !== '') {
 +                                      options.push(elData(listItem, 'option-id') + '_' + optionValue);
 +                              }
 +                      }
 +                      
 +                      return options;
 +              }
 +      };
 +      
 +      return UiPollEditor;
 +});
Simple merge
Simple merge