Merge branch 'master' into next
authorAlexander Ebert <ebert@woltlab.com>
Wed, 26 Jun 2019 10:40:32 +0000 (12:40 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Wed, 26 Jun 2019 10:40:32 +0000 (12:40 +0200)
1  2 
com.woltlab.wcf/templates/quoteMetaCode.tpl
com.woltlab.wcf/templates/register.tpl
wcfsetup/install/files/js/3rdParty/redactor2/redactor.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/Format.js
wcfsetup/install/files/lib/system/html/input/node/HtmlInputNodeProcessor.class.php

index 82eb8177b3bec127e6c9557a89ce13852a6250a9,6d5ab2a6957117defdaae89b03843993d4ea0c34..7946618a2ca3d8e0508e54fc3bacd10910c987f4
  
  <form method="post" action="{link controller='Register'}{/link}">
        <div class="section">
-               <dl{if $errorType.username|isset} class="formError"{/if}>
+               <dl{if $errorType[username]|isset} class="formError"{/if}>
                        <dt>
 -                              <label for="{@$randomFieldNames[username]}">{lang}wcf.user.username{/lang}</label>
 +                              <label for="{@$randomFieldNames[username]}">{lang}wcf.user.username{/lang}</label> <span class="customOptionRequired">*</span>
                        </dt>
                        <dd>
 -                              <input type="text" id="{@$randomFieldNames[username]}" name="{@$randomFieldNames[username]}" value="{$username}" required class="medium">
 +                              <input type="text" id="{@$randomFieldNames[username]}" name="{@$randomFieldNames[username]}" value="{$username}" required class="medium" autocomplete="username">
-                               {if $errorType.username|isset}
+                               {if $errorType[username]|isset}
                                        <small class="innerError">
-                                               {if $errorType.username == 'empty'}{lang}wcf.global.form.error.empty{/lang}{/if}
-                                               {if $errorType.username == 'invalid'}{lang}wcf.user.username.error.invalid{/lang}{/if}
-                                               {if $errorType.username == 'notUnique'}{lang}wcf.user.username.error.notUnique{/lang}{/if}
+                                               {if $errorType[username] == 'empty'}
+                                                       {lang}wcf.global.form.error.empty{/lang}
+                                               {else}
+                                                       {lang}wcf.user.username.error.{$errorType[username]}{/lang}
+                                               {/if}
                                        </small>
                                {/if}
                                <small>{lang}wcf.user.username.description{/lang}</small>
@@@ -66,9 -68,9 +68,9 @@@
        <section class="section">
                <h2 class="sectionTitle">{lang}wcf.user.email{/lang}</h2>
                
-               <dl{if $errorType.email|isset} class="formError"{/if}>
+               <dl{if $errorType[email]|isset} class="formError"{/if}>
                        <dt>
 -                              <label for="{@$randomFieldNames[email]}">{lang}wcf.user.email{/lang}</label>
 +                              <label for="{@$randomFieldNames[email]}">{lang}wcf.user.email{/lang}</label> <span class="customOptionRequired">*</span>
                        </dt>
                        <dd>
                                <input type="email" id="{@$randomFieldNames[email]}" name="{@$randomFieldNames[email]}" value="{$email}" required class="medium">
@@@ -82,9 -86,9 +86,9 @@@
                        </dd>
                </dl>
                
-               <dl{if $errorType.confirmEmail|isset} class="formError"{/if}>
+               <dl{if $errorType[confirmEmail]|isset} class="formError"{/if}>
                        <dt>
 -                              <label for="{@$randomFieldNames[confirmEmail]}">{lang}wcf.user.confirmEmail{/lang}</label>
 +                              <label for="{@$randomFieldNames[confirmEmail]}">{lang}wcf.user.confirmEmail{/lang}</label> <span class="customOptionRequired">*</span>
                        </dt>
                        <dd>
                                <input type="email" id="{@$randomFieldNames[confirmEmail]}" name="{@$randomFieldNames[confirmEmail]}" value="{$confirmEmail}" required class="medium">
                <section class="section">
                        <h2 class="sectionTitle">{lang}wcf.user.password{/lang}</h2>
                        
-                       <dl{if $errorType.password|isset} class="formError"{/if}>
+                       <dl{if $errorType[password]|isset} class="formError"{/if}>
                                <dt>
 -                                      <label for="{@$randomFieldNames[password]}">{lang}wcf.user.password{/lang}</label>
 +                                      <label for="{@$randomFieldNames[password]}">{lang}wcf.user.password{/lang}</label> <span class="customOptionRequired">*</span>
                                </dt>
                                <dd>
 -                                      <input type="password" id="{@$randomFieldNames[password]}" name="{@$randomFieldNames[password]}" value="{$password}" required class="medium">
 +                                      <input type="password" id="{@$randomFieldNames[password]}" name="{@$randomFieldNames[password]}" value="{$password}" required class="medium" autocomplete="new-password" passwordrules="{$passwordRulesAttributeValue}">
-                                       {if $errorType.password|isset}
+                                       {if $errorType[password]|isset}
                                                <small class="innerError">
-                                                       {if $errorType.password == 'empty'}{lang}wcf.global.form.error.empty{/lang}{/if}
-                                                       {if $errorType.password == 'notSecure'}{lang}wcf.user.password.error.notSecure{/lang}{/if}
+                                                       {if $errorType[password] == 'empty'}
+                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                       {else}
+                                                               {lang}wcf.user.password.error.{$errorType[password]}{/lang}
+                                                       {/if}
                                                </small>
                                        {/if}
                                        <small>{lang}wcf.user.password.description{/lang}</small>
                                </dd>
                        </dl>
                        
-                       <dl{if $errorType.confirmPassword|isset} class="formError"{/if}>
+                       <dl{if $errorType[confirmPassword]|isset} class="formError"{/if}>
                                <dt>
 -                                      <label for="{@$randomFieldNames[confirmPassword]}">{lang}wcf.user.confirmPassword{/lang}</label>
 +                                      <label for="{@$randomFieldNames[confirmPassword]}">{lang}wcf.user.confirmPassword{/lang}</label> <span class="customOptionRequired">*</span>
                                </dt>
                                <dd>
 -                                      <input type="password" id="{@$randomFieldNames[confirmPassword]}" name="{@$randomFieldNames[confirmPassword]}" value="{$confirmPassword}" required class="medium">
 +                                      <input type="password" id="{@$randomFieldNames[confirmPassword]}" name="{@$randomFieldNames[confirmPassword]}" value="{$confirmPassword}" required class="medium" autocomplete="new-password" passwordrules="{$passwordRulesAttributeValue}">
-                                       {if $errorType.confirmPassword|isset}
+                                       {if $errorType[confirmPassword]|isset}
                                                <small class="innerError">
-                                                       {if $errorType.confirmPassword == 'notEqual'}{lang}wcf.user.confirmPassword.error.notEqual{/lang}{/if}
+                                                       {lang}wcf.user.confirmPassword.error.{$errorType[confirmPassword]}{/lang}
                                                </small>
                                        {/if}
                                </dd>
index a9f5d6e201ee1655d453fe7241729423445158d7,232a598a2a3176597627fd575e95349e60b9c973..b6bc074c4cc2c7b13a8c85e6ae919d93066e6faf
                                                        newInline = this.inline.insertInline(tag);
                                                        newInline = this.inline.setParams(newInline, params);
                                                }
 -                                              else
 -                                              {
 -                                              var $first = this.inline.insertBreakpoint(inline, currentTag);
 -                            this.caret.after($first);
 -                                              }
 -                              }
 -                              else
 -                              {
 -                        newInline = this.inline.insertInline(tag);
 -                        newInline = this.inline.setParams(newInline, params);
 -                              }
 -                              },
 -                              formatUncollapsed: function(tag, params)
 -                              {
 -                              this.selection.save();
 -
 -                    var nodes = this.inline.getClearedNodes();
 -                    this.inline.setNodesStriked(nodes, tag, params);
 -
 -                    this.selection.restore();
 -
 -                    document.execCommand('strikethrough');
 +                                              else {
 +                                                      var $first = this.inline.insertBreakpoint(inline, currentTag);
 +                                                      this.caret.after($first);
 -                    this.selection.saveInstant();
 +                                              }
 +                                      }
 +                                      else {
 +                                              newInline = this.inline.insertInline(tag);
 +                                              newInline = this.inline.setParams(newInline, params);
 +                                      }
 +                              },
 +                              formatUncollapsed: function (tag, params) {
 +                                      this.selection.save();
 +                                      
 +                                      var nodes = this.inline.getClearedNodes();
 +                                      this.inline.setNodesStriked(nodes, tag, params);
 +                                      
 +                                      this.selection.restore();
 +                                      
 +                                      document.execCommand('strikethrough');
 +                                      
 +                                      this.selection.saveInstant();
+                                       // WoltLab: Chrome misbehaves in some cases, causing the `<strike>` element for
+                                       // contained elements to be stripped. Instead, those children are assigned the
+                                       // CSS style `text-decoration-line: line-through`.
+                                       var chromeElements = this.core.editor()[0].querySelectorAll('[style*="line-through"]'), element, strike;
+                                       for (var i = 0, length = chromeElements.length; i < length; i++) {
+                                               element = chromeElements[0];
 -                                              
++
+                                               strike = document.createElement('strike');
+                                               element.parentNode.insertBefore(strike, element);
+                                               strike.appendChild(element);
 -                                              
++
+                                               // Remove the bogus style attribute.
+                                               element.style.removeProperty('text-decoration');
+                                       }
                                        
                                        var self = this;
 -                    this.core.editor().find('strike').each(function()
 -                              {
 -                                      var $el = self.utils.replaceToTag(this, tag);
 -                                      self.inline.setParams($el[0], params);
 -
 -                                      var $inside = $el.find(tag);
 -                                      var $parent = $el.parent();
 -                                      var $parentAround = $parent.parent();
 -
 -                        // revert formatting (safari bug)
 -                        if ($parentAround.length !== 0 && $parentAround[0].tagName.toLowerCase() === tag && $parentAround.html() == $parent[0].outerHTML)
 -                        {
 -                            $el.replaceWith(function() { return $(this).contents(); });
 -                            $parentAround.replaceWith(function() { return $(this).contents(); });
 -
 -                            return;
 -                        }
 -
 -                                      // remove inside
 -                                      if ($inside.length !== 0) self.inline.cleanInsideOrParent($inside, params);
 -
 -                        // same parent
 -                        if ($parent.html() == $el[0].outerHTML) self.inline.cleanInsideOrParent($parent, params);
 -
 -                        // bugfix: remove empty inline tags after selection
 -                        if (self.detect.isFirefox())
 -                        {
 -                            self.core.editor().find(tag + ':empty').remove();
 -                        }
 -                              });
 -
 -                              this.selection.restoreInstant();
 -                              },
 -                              cleanInsideOrParent: function($el, params)
 -                              {
 -                    if (params)
 -                    {
 -                        for (var key in params.data)
 -                        {
 -                                          this.inline.removeSpecificAttr($el, key, params.data[key]);
 -                                      }
 -                              }
 -                              },
 -                      getClearedNodes: function()
 -                      {
 -                    var nodes = this.selection.nodes();
 -                              var newNodes = [];
 -                              var len = nodes.length;
 -                    var started = 0;
 -
 -                    // find array slice
 -                    for (var i = 0; i < len; i++)
 -                                      {
 -                                              if ($(nodes[i]).hasClass('redactor-selection-marker'))
 -                                              {
 -                                              started = i + 2;
 -                                              break;
 +                                      this.core.editor().find('strike').each(function () {
 +                                              var $el = self.utils.replaceToTag(this, tag);
 +                                              self.inline.setParams($el[0], params);
 +                                              
 +                                              var $inside = $el.find(tag);
 +                                              var $parent = $el.parent();
 +                                              var $parentAround = $parent.parent();
 +                                              
 +                                              // revert formatting (safari bug)
 +                                              if ($parentAround.length !== 0 && $parentAround[0].tagName.toLowerCase() === tag && $parentAround.html() == $parent[0].outerHTML) {
 +                                                      $el.replaceWith(function () { return $(this).contents(); });
 +                                                      $parentAround.replaceWith(function () { return $(this).contents(); });
 +                                                      
 +                                                      return;
 +                                              }
 +                                              
 +                                              // remove inside
 +                                              if ($inside.length !== 0) {
 +                                                      self.inline.cleanInsideOrParent($inside, params);
 +                                              }
 +                                              
 +                                              // same parent
 +                                              if ($parent.html() == $el[0].outerHTML) {
 +                                                      self.inline.cleanInsideOrParent($parent, params);
 +                                              }
 +                                              
 +                                              // bugfix: remove empty inline tags after selection
 +                                              if (self.detect.isFirefox()) {
 +                                                      self.core.editor().find(tag + ':empty').remove();
 +                                              }
 +                                      });
 +                                      
 +                                      this.selection.restoreInstant();
 +                              },
 +                              cleanInsideOrParent: function ($el, params) {
 +                                      if (params) {
 +                                              for (var key in params.data) {
 +                                                      this.inline.removeSpecificAttr($el, key, params.data[key]);
                                                }
                                        }
 -
 -                    // find selected inline & text nodes
 -                    for (var i = 0; i < len; i++)
 -                                      {
 -                                              if (i >= started && !this.utils.isBlockTag(nodes[i].tagName))
 -                                              {
 -                            newNodes.push(nodes[i]);
 +                              },
 +                              getClearedNodes: function () {
 +                                      var nodes = this.selection.nodes();
 +                                      var newNodes = [];
 +                                      var len = nodes.length;
 +                                      var started = 0;
 +                                      
 +                                      // find array slice
 +                                      for (var i = 0; i < len; i++) {
 +                                              if ($(nodes[i]).hasClass('redactor-selection-marker')) {
 +                                                      started = i + 2;
 +                                                      break;
                                                }
                                        }
 -
 +                                      
 +                                      // find selected inline & text nodes
 +                                      for (var i = 0; i < len; i++) {
 +                                              if (i >= started && !this.utils.isBlockTag(nodes[i].tagName)) {
 +                                                      newNodes.push(nodes[i]);
 +                                              }
 +                                      }
 +                                      
                                        return newNodes;
 -                      },
 -                isConvertableAttr: function(node, name, value)
 -                {
 -                    var nodeAttrValue = $(node).attr(name);
 -                    if (nodeAttrValue)
 -                    {
 -                        if (name === 'style')
 -                        {
 -                            value = $.trim(value).replace(/;$/, '')
 -
 -                            var rules = value.split(';');
 -                            var count = 0;
 -                            for (var i = 0; i < rules.length; i++)
 -                            {
 -                                var arr = rules[i].split(':');
 -                                var ruleName = $.trim(arr[0]);
 -                                var ruleValue = $.trim(arr[1]);
 -
 -                                if (ruleName.search(/color/) !== -1)
 -                                {
 -                                    var val = $(node).css(ruleName);
 -                                    if (val && (val === ruleValue || this.utils.rgb2hex(val) === ruleValue))
 -                                    {
 -                                        count++;
 -                                    }
 -                                }
 -                                else if ($(node).css(ruleName) === ruleValue)
 -                                {
 -                                    count++;
 -                                }
 -                            }
 -
 -                            if (count === rules.length)
 -                            {
 -                                return 1;
 -                            }
 -                        }
 -                        else if (nodeAttrValue === value)
 -                        {
 -                            return 1;
 -                        }
 -                    }
 -
 -                    return 0;
 -
 -                },
 -                      isConvertable: function(node, nodeTag, tag, params)
 -                      {
 -                    if (nodeTag === tag)
 -                    {
 -                        if (params)
 -                        {
 -                            var count = 0;
 -                            for (var key in params.data)
 -                            {
 -                                count += this.inline.isConvertableAttr(node, key, params.data[key]);
 -                            }
 -
 -                            if (count === Object.keys(params.data).length)
 -                            {
 -                                return true;
 -                            }
 -                        }
 -                        else
 -                        {
 -                            return true;
 -                        }
 -                    }
 -
 -                    return false;
 -                      },
 -                      setNodesStriked: function(nodes, tag, params)
 -                      {
 -                    for (var i = 0; i < nodes.length; i++)
 -                                      {
 -                        var nodeTag = (nodes[i].tagName) ? nodes[i].tagName.toLowerCase() : undefined;
 -
 -                        var parent = nodes[i].parentNode;
 -                        var parentTag = (parent && parent.tagName) ? parent.tagName.toLowerCase() : undefined;
 -
 -                        var convertable = this.inline.isConvertable(parent, parentTag, tag, params);
 -                        if (convertable)
 -                        {
 -                            var $el = $(parent).replaceWith(function()
 -                            {
 -                                return $('<strike>').append($(this).contents());
 -                            });
 -
 -                            $el.attr('data-redactor-inline-converted');
 -                        }
 -
 -                        var convertable = this.inline.isConvertable(nodes[i], nodeTag, tag, params);
 -                        if (convertable)
 -                        {
 -                            var $el = $(nodes[i]).replaceWith(function()
 -                            {
 -                                return $('<strike>').append($(this).contents());
 -                            });
 -                        }
 -                              }
 -                      },
 -                insertBreakpoint: function(inline, currentTag)
 -                              {
 +                              },
 +                              isConvertableAttr: function (node, name, value) {
 +                                      var nodeAttrValue = $(node).attr(name);
 +                                      if (nodeAttrValue) {
 +                                              if (name === 'style') {
 +                                                      value = $.trim(value).replace(/;$/, '');
 +                                                      
 +                                                      var rules = value.split(';');
 +                                                      var count = 0;
 +                                                      for (var i = 0; i < rules.length; i++) {
 +                                                              var arr = rules[i].split(':');
 +                                                              var ruleName = $.trim(arr[0]);
 +                                                              var ruleValue = $.trim(arr[1]);
 +                                                              
 +                                                              if (ruleName.search(/color/) !== -1) {
 +                                                                      var val = $(node).css(ruleName);
 +                                                                      if (val && (val === ruleValue || this.utils.rgb2hex(
 +                                                                              val) === ruleValue)) {
 +                                                                              count++;
 +                                                                      }
 +                                                              }
 +                                                              else if ($(node).css(ruleName) === ruleValue) {
 +                                                                      count++;
 +                                                              }
 +                                                      }
 +                                                      
 +                                                      if (count === rules.length) {
 +                                                              return 1;
 +                                                      }
 +                                              }
 +                                              else if (nodeAttrValue === value) {
 +                                                      return 1;
 +                                              }
 +                                      }
 +                                      
 +                                      return 0;
 +                                      
 +                              },
 +                              isConvertable: function (node, nodeTag, tag, params) {
 +                                      if (nodeTag === tag) {
 +                                              if (params) {
 +                                                      var count = 0;
 +                                                      for (var key in params.data) {
 +                                                              count += this.inline.isConvertableAttr(node,
 +                                                                      key,
 +                                                                      params.data[key]
 +                                                              );
 +                                                      }
 +                                                      
 +                                                      if (count === Object.keys(params.data).length) {
 +                                                              return true;
 +                                                      }
 +                                              }
 +                                              else {
 +                                                      return true;
 +                                              }
 +                                      }
 +                                      
 +                                      return false;
 +                              },
 +                              setNodesStriked: function (nodes, tag, params) {
 +                                      for (var i = 0; i < nodes.length; i++) {
 +                                              var nodeTag = (nodes[i].tagName) ? nodes[i].tagName.toLowerCase() : undefined;
 +                                              
 +                                              var parent = nodes[i].parentNode;
 +                                              var parentTag = (parent && parent.tagName) ? parent.tagName.toLowerCase() : undefined;
 +                                              
 +                                              var convertable = this.inline.isConvertable(parent,
 +                                                      parentTag,
 +                                                      tag,
 +                                                      params
 +                                              );
 +                                              if (convertable) {
 +                                                      var $el = $(parent).replaceWith(function () {
 +                                                              return $('<strike>').append($(this).contents());
 +                                                      });
 +                                                      
 +                                                      $el.attr('data-redactor-inline-converted');
 +                                              }
 +                                              
 +                                              var convertable = this.inline.isConvertable(nodes[i],
 +                                                      nodeTag,
 +                                                      tag,
 +                                                      params
 +                                              );
 +                                              if (convertable) {
 +                                                      var $el = $(nodes[i]).replaceWith(function () {
 +                                                              return $('<strike>').append($(this).contents());
 +                                                      });
 +                                              }
 +                                      }
 +                              },
 +                              insertBreakpoint: function (inline, currentTag) {
                                        var breakpoint = document.createElement('span');
                                        breakpoint.id = 'redactor-inline-breakpoint';
                                        breakpoint = this.insert.node(breakpoint);
                                this.$element.css(this.prefixes[len] + 'animation-iteration-count', iterate);
                                this.$element.css(this.prefixes[len] + 'animation-timing-function', timing);
                        }
 -
 +                      
                },
 -              clean: function()
 -              {
 +              clean: function () {
                        this.$element.removeClass('redactor-animated');
                        this.$element.removeClass(this.opts.prefix + this.queue[0]);
 -
 +                      
                        this.set('', '', '', '');
 -
 +                      
                },
 -              complete: function(type, make, callback)
 -              {
 -                      this.$element.one(type.toLowerCase() + ' webkit' + type + ' o' + type + ' MS' + type, $.proxy(function()
 -                      {
 -                              if (typeof make === 'function')
 -                              {
 -                                      make();
 -                              }
 -
 -                              if (typeof callback === 'function')
 -                              {
 -                                      callback(this);
 -                              }
 -
 -                              // hide
 -                              var effects = ['fadeOut', 'slideUp', 'zoomOut', 'slideOutUp', 'slideOutRight', 'slideOutLeft'];
 -                              if ($.inArray(this.animation, effects) !== -1)
 -                              {
 -                                      this.$element.css('display', 'none');
 -                              }
 -
 -                              // slide
 -                              if (this.slide)
 -                              {
 -                                      this.$element.css('height', '');
 -                              }
 -
 -                      }, this));
 -
 +              complete: function (type, make, callback) {
 +                      this.$element.one(type.toLowerCase() + ' webkit' + type + ' o' + type + ' MS' + type,
 +                              $.proxy(function () {
 +                                      if (typeof make === 'function') {
 +                                              make();
 +                                      }
 +                                      
 +                                      if (typeof callback === 'function') {
 +                                              callback(this);
 +                                      }
 +                                      
 +                                      // hide
 +                                      var effects = [
 +                                              'fadeOut',
 +                                              'slideUp',
 +                                              'zoomOut',
 +                                              'slideOutUp',
 +                                              'slideOutRight',
 +                                              'slideOutLeft'
 +                                      ];
 +                                      if ($.inArray(this.animation, effects) !== -1) {
 +                                              this.$element.css('display', 'none');
 +                                      }
 +                                      
 +                                      // slide
 +                                      if (this.slide) {
 +                                              this.$element.css('height', '');
 +                                      }
 +                                      
 +                              }, this)
 +                      );
 +                      
                }
        };
-       
 -
  })(jQuery);
index 3830e7fecb13f326d21236882c796953cefb246a,d69ee459082447ad78e92e9864fbbc33d5e1da5a..61cd89a7008a80db603beb5472e925ea47f681b4
@@@ -72,14 -71,22 +72,27 @@@ class HtmlInputNodeProcessor extends Ab
                'ul', 'ol', 'li',
                
                // other
 -              'a', 'kbd', 'woltlab-quote', 'woltlab-spoiler', 'pre', 'sub', 'sup'
 +              'a', 'kbd', 'woltlab-quote', 'woltlab-spoiler', 'pre', 'sub', 'sup',
        ];
        
+       /**
+        * list of tag names that represent inline content in the HTML 5 standard
+        * @var string[]
+        */
+       public static $inlineElements = [
+               'a', 'abbr', 'acronym', 'audio', 'b', 'bdi', 'bdo', 'big', 'br', 'button',
+               'canvas', 'cite', 'code', 'data', 'datalist', 'del', 'dfn', 'em', 'embed',
+               'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'label', 'map', 'mark', 'meter',
+               'noscript', 'object', 'output', 'picture', 'progress', 'q', 'ruby', 's',
+               'samp', 'script', 'select', 'slot', 'small', 'span', 'strong', 'sub', 'sup',
+               'svg', 'template', 'textarea', 'time', 'u', 'tt', 'var', 'video', 'wbr',
+       ];
+       
 +      /**
 +       * @var HtmlNodePlainLink[]
 +       */
 +      public $plainLinks = [];
 +      
        /**
         * list of embedded content grouped by type
         * @var array