Improved `<kbd>` behavior
authorAlexander Ebert <ebert@woltlab.com>
Thu, 2 Nov 2017 14:50:50 +0000 (15:50 +0100)
committerAlexander Ebert <ebert@woltlab.com>
Thu, 2 Nov 2017 14:50:50 +0000 (15:50 +0100)
wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabCaret.js
wcfsetup/install/files/style/bbcode/inlineCode.scss

index 01b87d3dc35f8e02996edeb59bbf6fe63b6e23fe..8515169f9db8155773f696bd1861814a48b778f0 100644 (file)
@@ -69,6 +69,7 @@ $.Redactor.prototype.WoltLabCaret = function() {
                        }).bind(this);
                        
                        this.$editor[0].addEventListener(WCF_CLICK_EVENT, this.WoltLabCaret._handleEditorClick.bind(this));
+                       this.$editor[0].addEventListener('mouseup', this.WoltLabCaret._handleEditorMouseUp.bind(this));
                        
                        this.WoltLabCaret._initInternalRange();
                        
@@ -378,6 +379,59 @@ $.Redactor.prototype.WoltLabCaret = function() {
                        this.caret.end(p);
                },
                
+               _handleEditorMouseUp: function (event) {
+                       var anchorNode, sibling;
+                       
+                       var selection = window.getSelection();
+                       if (!selection.isCollapsed) return;
+                       
+                       // click occured inside the editor padding
+                       if (event.target === this.$editor[0]) {
+                               anchorNode = selection.anchorNode;
+                               if (anchorNode.nodeType === Node.TEXT_NODE) anchorNode = anchorNode.parentNode;
+                               
+                               // click occured before a `<kbd>` element
+                               if (anchorNode.nodeName === 'KBD') {
+                                       sibling = anchorNode.previousSibling;
+                                       if (sibling === null || sibling.textContent !== '\u200b') {
+                                               sibling = document.createTextNode('\u200b');
+                                               anchorNode.parentNode.insertBefore(sibling, anchorNode);
+                                       }
+                                       
+                                       this.caret.before(sibling);
+                               }
+                       }
+                       else if (event.target.nodeName === 'KBD') {
+                               var kbd = event.target;
+                               
+                               // check if the user clicked on a `<kbd>` element, but the browser placed the caret to the left
+                               anchorNode = selection.anchorNode;
+                               if (anchorNode.nodeType === Node.TEXT_NODE) {
+                                       // check if the first next sibling is the `<kbd>` while skipping all empty text nodes
+                                       sibling = anchorNode;
+                                       while (sibling = sibling.nextSibling) {
+                                               if (sibling.nodeType !== Node.TEXT_NODE || (sibling.textContent !== '' && sibling.textContent !== '\u200b')) {
+                                                       break;
+                                               }
+                                       }
+                                       
+                                       if (sibling === kbd) {
+                                               if (kbd.childNodes.length === 0 || kbd.childNodes[0].textContent !== '\u200b') {
+                                                       var textNode = document.createTextNode('\u200b');
+                                                       kbd.insertBefore(textNode, kbd.firstChild);
+                                               }
+                                               
+                                               var range = document.createRange();
+                                               range.setStartAfter(kbd.childNodes[0]);
+                                               range.setEndAfter(kbd.childNodes[0]);
+                                               
+                                               selection.removeAllRanges();
+                                               selection.addRange(range);
+                                       }
+                               }
+                       }
+               },
+               
                _addParagraphAfterBlock: function (block) {
                        var nextElement = block.nextElementSibling;
                        if (nextElement && (nextElement.nodeName === 'P' || this.utils.isBlockTag(nextElement.nodeName))) {
index 2f158752e941415fa5b17759eb27069afabe54e7..3be03a958a6a8fc95ffad15a56b91e6709144fcc 100644 (file)
@@ -1,17 +1,24 @@
 .inlineCode, /* deprecated, legacy class */
 kbd {
+       /* do not use inline-block, it breaks arrow key navigation in Firefox and Internet Explorer 11 */
+       
+       /* update: `inline` styling breaks even more things, in particular the caret position is way off */
+       /* this reverts 8d381dc61e8183adcb770457f9fba25c29c00bd2 */
+       
+       /* new update: `display: inline` + `box-decoration-break` deliver the proper visual appearance,
+                      and the `::after` element in the editor is used to fix the caret position at the end */
+       
        background-color: rgba(255, 255, 255, 1) !important;
        border: 1px solid rgba(196, 196, 196, 1) !important;
        border-radius: 2px;
+       box-decoration-break: clone;
+       -webkit-box-decoration-break: clone;
        color: rgba(68, 68, 68, 1) !important;
-       /* do not use inline-block, it breaks arrow key navigation in Firefox and Internet Explorer 11 */
-       /* update: `inline` styling breaks even more things, in particular the caret position is way off */
-       /* this reverts 8d381dc61e8183adcb770457f9fba25c29c00bd2 */
-       display: inline-block;
+       display: inline;
        font-family: Consolas, 'Courier New', monospace;
        font-style: normal;
        font-weight: normal;
-       margin: 1px 2px;
+       margin: 0 2px;
        overflow: auto;
        padding: 0 4px;
        text-decoration: none;
@@ -19,3 +26,21 @@ kbd {
        word-break: break-all;
        word-wrap: break-word;
 }
+
+/* This pseudo element will cause a trailing caret to be displayed inside the element, right after
+   the last character in the `<kbd>`. Without it, browsers may render the caret either on top or
+   slightly after the right border. */
+.redactor-layer kbd::after {
+       content: " ";
+       display: inline-block;
+       pointer-events: none;
+}
+
+/* Similar to the `::after` pseudo element above, but also features an absolute positioning. This
+   has no impact on the visual appearance, but avoids the caret being displayed shifted to the bottom. */
+.redactor-layer kbd::before {
+       content: " ";
+       display: inline-block;
+       pointer-events: none;
+       position: absolute;
+}