Improve the visuals and accessibility of the media image widget
authorAlexander Ebert <ebert@woltlab.com>
Tue, 19 Sep 2023 12:21:32 +0000 (14:21 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Tue, 19 Sep 2023 12:21:32 +0000 (14:21 +0200)
See https://www.woltlab.com/community/thread/301671-bildauswahl-boxen/

com.woltlab.wcf/templates/__singleMediaSelectionFormField.tpl
com.woltlab.wcf/templates/articleAdd.tpl
ts/WoltLabSuite/Core/Media/Manager/Select.ts
wcfsetup/install/files/acp/templates/__singleMediaSelectionFormField.tpl
wcfsetup/install/files/acp/templates/articleAdd.tpl
wcfsetup/install/files/acp/templates/boxAdd.tpl
wcfsetup/install/files/js/WoltLabSuite/Core/Media/Manager/Select.js

index 67ae54f71a161c60a7f8afbafdcab38caad74827..26339b8a5d19552f48b69ee739cfdc7c8ae74ef7 100644 (file)
                        {/if}
                </div>
        {/if}
-       <p class="button jsMediaSelectButton jsMediaSelectButton_{$field->getPrefixedId()}" data-store="{$field->getPrefixedId()}"{if $field->isImageOnly()} data-display="{$field->getPrefixedId()}_preview"{/if}>{lang}wcf.media.choose{if $field->isImageOnly()}Image{else}File{/if}{/lang}</p>
+       <ul class="buttonGroup">
+               <li>
+                       <button type="button" class="button jsMediaSelectButton jsMediaSelectButton_{$field->getPrefixedId()}" data-store="{$field->getPrefixedId()}"{if $field->isImageOnly()} data-display="{$field->getPrefixedId()}_preview"{/if}>{lang}wcf.media.choose{if $field->isImageOnly()}Image{else}File{/if}{/lang}</button>
+               </li>
+       </ul>
        <input type="hidden" name="{$field->getPrefixedId()}" id="{$field->getPrefixedId()}"{if $field->getValue()} value="{$field->getValue()}"{/if}>
        
        <script data-relocate="true">
index 3a2738d19e349c7fcd27987934b1e6e848e0f23d..79394c919e69cecf50b4b492d9fa3548cdaacaf2 100644 (file)
                                                                {@$images[0]->getThumbnailTag('small')}
                                                        {/if}
                                                </div>
-                                               <p class="button jsMediaSelectButton" data-store="imageID0" data-display="imageDisplay">{lang}wcf.media.chooseImage{/lang}</p>
+                                               <ul class="buttonGroup">
+                                                       <li>
+                                                               <button type="button" class="button jsMediaSelectButton" data-store="imageID0" data-display="imageDisplay">{lang}wcf.media.chooseImage{/lang}</button>
+                                                       </li>
+                                               </ul>
                                                <input type="hidden" name="imageID[0]" id="imageID0"{if $imageID[0]|isset} value="{$imageID[0]}"{/if}>
                                                {if $errorField == 'image'}
                                                        <small class="innerError">{lang}wcf.acp.article.image.error.{@$errorType}{/lang}</small>
                                                                {@$teaserImages[0]->getThumbnailTag('small')}
                                                        {/if}
                                                </div>
-                                               <p class="button jsMediaSelectButton" data-store="teaserImageID0" data-display="teaserImageDisplay">{lang}wcf.media.chooseImage{/lang}</p>
+                                               <ul class="buttonGroup">
+                                                       <li>
+                                                               <button type="button" class="button jsMediaSelectButton" data-store="teaserImageID0" data-display="teaserImageDisplay">{lang}wcf.media.chooseImage{/lang}</button>
+                                                       </li>
+                                               </ul>
                                                <input type="hidden" name="teaserImageID[0]" id="teaserImageID0"{if $teaserImageID[0]|isset} value="{$teaserImageID[0]}"{/if}>
                                                {if $errorField == 'teaserImage'}
                                                        <small class="innerError">{lang}wcf.acp.article.image.error.{@$errorType}{/lang}</small>
                                                                                        {@$images[$availableLanguage->languageID]->getThumbnailTag('small')}
                                                                                {/if}
                                                                        </div>
-                                                                       <p class="button jsMediaSelectButton" data-store="imageID{@$availableLanguage->languageID}" data-display="imageDisplay{@$availableLanguage->languageID}">{lang}wcf.media.chooseImage{/lang}</p>
+                                                                       <ul class="buttonGroup">
+                                                                               <li>
+                                                                                       <button type="button" class="button jsMediaSelectButton" data-store="imageID{@$availableLanguage->languageID}" data-display="imageDisplay{@$availableLanguage->languageID}">{lang}wcf.media.chooseImage{/lang}</button>
+                                                                               </li>
+                                                                       </ul>
                                                                        <input type="hidden" name="imageID[{@$availableLanguage->languageID}]" id="imageID{@$availableLanguage->languageID}"{if $imageID[$availableLanguage->languageID]|isset} value="{$imageID[$availableLanguage->languageID]}"{/if}>
                                                                        {if $errorField == 'image'|concat:$availableLanguage->languageID}
                                                                                <small class="innerError">{lang}wcf.acp.article.image.error.{@$errorType}{/lang}</small>
                                                                                        {@$teaserImages[$availableLanguage->languageID]->getThumbnailTag('small')}
                                                                                {/if}
                                                                        </div>
-                                                                       <p class="button jsMediaSelectButton" data-store="teaserImageID{@$availableLanguage->languageID}" data-display="teaserImageDisplay{@$availableLanguage->languageID}">{lang}wcf.media.chooseImage{/lang}</p>
+                                                                       <ul class="buttonGroup">
+                                                                               <li>
+                                                                                       <button type="button" class="button jsMediaSelectButton" data-store="teaserImageID{@$availableLanguage->languageID}" data-display="teaserImageDisplay{@$availableLanguage->languageID}">{lang}wcf.media.chooseImage{/lang}</button>
+                                                                               </li>
+                                                                       </ul>
                                                                        <input type="hidden" name="teaserImageID[{@$availableLanguage->languageID}]" id="teaserImageID{@$availableLanguage->languageID}"{if $teaserImageID[$availableLanguage->languageID]|isset} value="{$teaserImageID[$availableLanguage->languageID]}"{/if}>
                                                                        {if $errorField == 'teaserImage'|concat:$availableLanguage->languageID}
                                                                                <small class="innerError">{lang}wcf.acp.article.image.error.{@$errorType}{/lang}</small>
index f364a9a058849040dd20266eb4353e394b3e3f26..440518dfd15461d4b55812f5b9231a5543f7b241 100644 (file)
@@ -37,19 +37,30 @@ class MediaManagerSelect extends MediaManager<MediaManagerSelectOptions> {
 
           this._storeElements.set(button, storeElement);
 
-          // add remove button
-          const removeButton = document.createElement("p");
-          removeButton.className = "button";
-          button.insertAdjacentElement("afterend", removeButton);
+          const removeButton = document.createElement("button");
+          removeButton.type = "button";
+          removeButton.classList.add("button", "jsTooltip");
+          removeButton.title = Language.getPhrase("wcf.global.button.delete");
+          removeButton.innerHTML = '<fa-icon name="xmark"></fa-icon>';
 
-          const icon = document.createElement("fa-icon");
-          icon.setIcon("xmark");
-          removeButton.appendChild(icon);
+          if (button.parentElement!.tagName === "LI") {
+            const listItem = document.createElement("li");
+            listItem.append(removeButton);
 
-          if (!storeElement.value) {
-            DomUtil.hide(removeButton);
+            button.parentElement!.insertAdjacentElement("afterend", listItem);
+
+            if (!storeElement.value) {
+              listItem.hidden = true;
+            }
+          } else {
+            button.insertAdjacentElement("afterend", removeButton);
+
+            if (!storeElement.value) {
+              removeButton.hidden = true;
+            }
           }
-          removeButton.addEventListener("click", (ev) => this._removeMedia(ev));
+
+          removeButton.addEventListener("click", () => this._removeMedia(button, removeButton));
         }
       }
     });
@@ -117,7 +128,13 @@ class MediaManagerSelect extends MediaManager<MediaManagerSelectOptions> {
     }
 
     // show remove button
-    (this._activeButton.nextElementSibling as HTMLElement).style.removeProperty("display");
+    if (this._activeButton.parentElement!.tagName === "LI") {
+      const removeButton = this._activeButton.parentElement!.nextElementSibling as HTMLLIElement;
+      removeButton.hidden = false;
+    } else {
+      const removeButton = this._activeButton.nextElementSibling as HTMLButtonElement;
+      removeButton.hidden = false;
+    }
 
     UiDialog.close(this);
   }
@@ -168,18 +185,17 @@ class MediaManagerSelect extends MediaManager<MediaManagerSelectOptions> {
   /**
    * Handles clicking on the remove button.
    */
-  protected _removeMedia(event: Event): void {
-    event.preventDefault();
-
-    const removeButton = event.currentTarget as HTMLSpanElement;
-    const button = removeButton.previousElementSibling as HTMLElement;
-
-    removeButton.remove();
+  protected _removeMedia(selectButton: HTMLElement, removeButton: HTMLElement): void {
+    if (removeButton.parentElement!.tagName === "LI") {
+      removeButton.parentElement!.hidden = true;
+    } else {
+      removeButton.hidden = true;
+    }
 
-    const input = document.getElementById(button.dataset.store!) as HTMLInputElement;
+    const input = document.getElementById(selectButton.dataset.store!) as HTMLInputElement;
     input.value = "";
     Core.triggerEvent(input, "change");
-    const display = button.dataset.display;
+    const display = selectButton.dataset.display;
     if (display) {
       const displayElement = document.getElementById(display);
       if (displayElement) {
index 67ae54f71a161c60a7f8afbafdcab38caad74827..26339b8a5d19552f48b69ee739cfdc7c8ae74ef7 100644 (file)
                        {/if}
                </div>
        {/if}
-       <p class="button jsMediaSelectButton jsMediaSelectButton_{$field->getPrefixedId()}" data-store="{$field->getPrefixedId()}"{if $field->isImageOnly()} data-display="{$field->getPrefixedId()}_preview"{/if}>{lang}wcf.media.choose{if $field->isImageOnly()}Image{else}File{/if}{/lang}</p>
+       <ul class="buttonGroup">
+               <li>
+                       <button type="button" class="button jsMediaSelectButton jsMediaSelectButton_{$field->getPrefixedId()}" data-store="{$field->getPrefixedId()}"{if $field->isImageOnly()} data-display="{$field->getPrefixedId()}_preview"{/if}>{lang}wcf.media.choose{if $field->isImageOnly()}Image{else}File{/if}{/lang}</button>
+               </li>
+       </ul>
        <input type="hidden" name="{$field->getPrefixedId()}" id="{$field->getPrefixedId()}"{if $field->getValue()} value="{$field->getValue()}"{/if}>
        
        <script data-relocate="true">
index 10cce32d640b9fb99af2acc1c2e30f8421f84feb..a718b08174638221e57d07094f22962131e2b6aa 100644 (file)
                                                                {@$images[0]->getThumbnailTag('small')}
                                                        {/if}
                                                </div>
-                                               <p class="button jsMediaSelectButton" data-store="imageID0" data-display="imageDisplay">{lang}wcf.media.chooseImage{/lang}</p>
+                                               <ul class="buttonGroup">
+                                                       <li>
+                                                               <button type="button" class="button jsMediaSelectButton" data-store="imageID0" data-display="imageDisplay">{lang}wcf.media.chooseImage{/lang}</button>
+                                                       </li>
+                                               </ul>
                                                <input type="hidden" name="imageID[0]" id="imageID0"{if $imageID[0]|isset} value="{$imageID[0]}"{/if}>
                                                {if $errorField == 'image'}
                                                        <small class="innerError">{lang}wcf.acp.article.image.error.{@$errorType}{/lang}</small>
                                                                {@$teaserImages[0]->getThumbnailTag('small')}
                                                        {/if}
                                                </div>
-                                               <p class="button jsMediaSelectButton" data-store="teaserImageID0" data-display="teaserImageDisplay">{lang}wcf.media.chooseImage{/lang}</p>
+                                               <ul class="buttonGroup">
+                                                       <li>
+                                                               <button type="button" class="button jsMediaSelectButton" data-store="teaserImageID0" data-display="teaserImageDisplay">{lang}wcf.media.chooseImage{/lang}</button>
+                                                       </li>
+                                               </ul>
                                                <input type="hidden" name="teaserImageID[0]" id="teaserImageID0"{if $teaserImageID[0]|isset} value="{$teaserImageID[0]}"{/if}>
                                                {if $errorField == 'teaserImage'}
                                                        <small class="innerError">{lang}wcf.acp.article.image.error.{@$errorType}{/lang}</small>
                                                                                        {@$images[$availableLanguage->languageID]->getThumbnailTag('small')}
                                                                                {/if}
                                                                        </div>
-                                                                       <p class="button jsMediaSelectButton" data-store="imageID{@$availableLanguage->languageID}" data-display="imageDisplay{@$availableLanguage->languageID}">{lang}wcf.media.chooseImage{/lang}</p>
+                                                                       <ul class="buttonGroup">
+                                                                               <li>
+                                                                                       <button type="button" class="button jsMediaSelectButton" data-store="imageID{@$availableLanguage->languageID}" data-display="imageDisplay{@$availableLanguage->languageID}">{lang}wcf.media.chooseImage{/lang}</button>
+                                                                               </li>
+                                                                       </ul>
                                                                        <input type="hidden" name="imageID[{@$availableLanguage->languageID}]" id="imageID{@$availableLanguage->languageID}"{if $imageID[$availableLanguage->languageID]|isset} value="{$imageID[$availableLanguage->languageID]}"{/if}>
                                                                        {if $errorField == 'image'|concat:$availableLanguage->languageID}
                                                                                <small class="innerError">{lang}wcf.acp.article.image.error.{@$errorType}{/lang}</small>
                                                                                        {@$teaserImages[$availableLanguage->languageID]->getThumbnailTag('small')}
                                                                                {/if}
                                                                        </div>
-                                                                       <p class="button jsMediaSelectButton" data-store="teaserImageID{@$availableLanguage->languageID}" data-display="teaserImageDisplay{@$availableLanguage->languageID}">{lang}wcf.media.chooseImage{/lang}</p>
+                                                                       <ul class="buttonGroup">
+                                                                               <li>
+                                                                                       <button type="button" class="button jsMediaSelectButton" data-store="teaserImageID{@$availableLanguage->languageID}" data-display="teaserImageDisplay{@$availableLanguage->languageID}">{lang}wcf.media.chooseImage{/lang}</button>
+                                                                               </li>
+                                                                       </ul>
                                                                        <input type="hidden" name="teaserImageID[{@$availableLanguage->languageID}]" id="teaserImageID{@$availableLanguage->languageID}"{if $teaserImageID[$availableLanguage->languageID]|isset} value="{$teaserImageID[$availableLanguage->languageID]}"{/if}>
                                                                        {if $errorField == 'teaserImage'|concat:$availableLanguage->languageID}
                                                                                <small class="innerError">{lang}wcf.acp.article.image.error.{@$errorType}{/lang}</small>
index 0559f086b6428820d3b6289dba91ce70f9e2e435..99fdd595a59838423c4987aa433a1ac418a6f9bb 100644 (file)
                                                                                {@$images[0]->getThumbnailTag('small')}
                                                                        {/if}
                                                                </div>
-                                                               <p class="button jsMediaSelectButton" data-store="imageID0" data-display="imageDisplay">{lang}wcf.media.chooseImage{/lang}</p>
+                                                               <ul class="buttonGroup">
+                                                                       <li>
+                                                                               <button type="button" class="button jsMediaSelectButton" data-store="imageID0" data-display="imageDisplay">{lang}wcf.media.chooseImage{/lang}</button>
+                                                                       </li>
+                                                               </ul>
                                                                <input type="hidden" name="imageID[0]" id="imageID0"{if $imageID[0]|isset} value="{$imageID[0]}"{/if}>
                                                                {if $errorField == 'image'}
                                                                        <small class="innerError">{lang}wcf.acp.box.image.error.{@$errorType}{/lang}</small>
                                                                                                                {@$images[$availableLanguage->languageID]->getThumbnailTag('small')}
                                                                                                        {/if}
                                                                                                </div>
-                                                                                               <p class="button jsMediaSelectButton" data-store="imageID{@$availableLanguage->languageID}" data-display="imageDisplay{@$availableLanguage->languageID}">{lang}wcf.media.chooseImage{/lang}</p>
+                                                                                               <ul class="buttonGroup">
+                                                                                                       <li>
+                                                                                                               <button type="button" class="button jsMediaSelectButton" data-store="imageID{@$availableLanguage->languageID}" data-display="imageDisplay{@$availableLanguage->languageID}">{lang}wcf.media.chooseImage{/lang}</button>
+                                                                                                       </li>
+                                                                                               </ul>
                                                                                                <input type="hidden" name="imageID[{@$availableLanguage->languageID}]" id="imageID{@$availableLanguage->languageID}"{if $imageID[$availableLanguage->languageID]|isset} value="{$imageID[$availableLanguage->languageID]}"{/if}>
                                                                                                {if $errorField == 'image'|concat:$availableLanguage->languageID}
                                                                                                        <small class="innerError">{lang}wcf.acp.box.image.error.{@$errorType}{/lang}</small>
index 7bb962168dbcdef82bcfba02a39d5ff9175821a2..d3718fc1865cf5f2cc3c33057df850b55bddeb7a 100644 (file)
@@ -6,7 +6,7 @@
  * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @woltlabExcludeBundle tiny
  */
-define(["require", "exports", "tslib", "./Base", "../../Core", "../../Dom/Traverse", "../../FileUtil", "../../Language", "../../Ui/Dialog", "../../Dom/Util"], function (require, exports, tslib_1, Base_1, Core, DomTraverse, FileUtil, Language, UiDialog, Util_1) {
+define(["require", "exports", "tslib", "./Base", "../../Core", "../../Dom/Traverse", "../../FileUtil", "../../Language", "../../Ui/Dialog"], function (require, exports, tslib_1, Base_1, Core, DomTraverse, FileUtil, Language, UiDialog) {
     "use strict";
     Base_1 = tslib_1.__importDefault(Base_1);
     Core = tslib_1.__importStar(Core);
@@ -14,7 +14,6 @@ define(["require", "exports", "tslib", "./Base", "../../Core", "../../Dom/Traver
     FileUtil = tslib_1.__importStar(FileUtil);
     Language = tslib_1.__importStar(Language);
     UiDialog = tslib_1.__importStar(UiDialog);
-    Util_1 = tslib_1.__importDefault(Util_1);
     class MediaManagerSelect extends Base_1.default {
         _activeButton = null;
         _buttons;
@@ -30,17 +29,26 @@ define(["require", "exports", "tslib", "./Base", "../../Core", "../../Dom/Traver
                     if (storeElement && storeElement.tagName === "INPUT") {
                         button.addEventListener("click", (ev) => this._click(ev));
                         this._storeElements.set(button, storeElement);
-                        // add remove button
-                        const removeButton = document.createElement("p");
-                        removeButton.className = "button";
-                        button.insertAdjacentElement("afterend", removeButton);
-                        const icon = document.createElement("fa-icon");
-                        icon.setIcon("xmark");
-                        removeButton.appendChild(icon);
-                        if (!storeElement.value) {
-                            Util_1.default.hide(removeButton);
+                        const removeButton = document.createElement("button");
+                        removeButton.type = "button";
+                        removeButton.classList.add("button", "jsTooltip");
+                        removeButton.title = Language.getPhrase("wcf.global.button.delete");
+                        removeButton.innerHTML = '<fa-icon name="xmark"></fa-icon>';
+                        if (button.parentElement.tagName === "LI") {
+                            const listItem = document.createElement("li");
+                            listItem.append(removeButton);
+                            button.parentElement.insertAdjacentElement("afterend", listItem);
+                            if (!storeElement.value) {
+                                listItem.hidden = true;
+                            }
                         }
-                        removeButton.addEventListener("click", (ev) => this._removeMedia(ev));
+                        else {
+                            button.insertAdjacentElement("afterend", removeButton);
+                            if (!storeElement.value) {
+                                removeButton.hidden = true;
+                            }
+                        }
+                        removeButton.addEventListener("click", () => this._removeMedia(button, removeButton));
                     }
                 }
             });
@@ -100,7 +108,14 @@ define(["require", "exports", "tslib", "./Base", "../../Core", "../../Dom/Traver
                 }
             }
             // show remove button
-            this._activeButton.nextElementSibling.style.removeProperty("display");
+            if (this._activeButton.parentElement.tagName === "LI") {
+                const removeButton = this._activeButton.parentElement.nextElementSibling;
+                removeButton.hidden = false;
+            }
+            else {
+                const removeButton = this._activeButton.nextElementSibling;
+                removeButton.hidden = false;
+            }
             UiDialog.close(this);
         }
         _click(event) {
@@ -141,15 +156,17 @@ define(["require", "exports", "tslib", "./Base", "../../Core", "../../Dom/Traver
         /**
          * Handles clicking on the remove button.
          */
-        _removeMedia(event) {
-            event.preventDefault();
-            const removeButton = event.currentTarget;
-            const button = removeButton.previousElementSibling;
-            removeButton.remove();
-            const input = document.getElementById(button.dataset.store);
+        _removeMedia(selectButton, removeButton) {
+            if (removeButton.parentElement.tagName === "LI") {
+                removeButton.parentElement.hidden = true;
+            }
+            else {
+                removeButton.hidden = true;
+            }
+            const input = document.getElementById(selectButton.dataset.store);
             input.value = "";
             Core.triggerEvent(input, "change");
-            const display = button.dataset.display;
+            const display = selectButton.dataset.display;
             if (display) {
                 const displayElement = document.getElementById(display);
                 if (displayElement) {