Add the missing language switch to the mobile guest menu
authorAlexander Ebert <ebert@woltlab.com>
Tue, 13 Sep 2022 15:10:51 +0000 (17:10 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Tue, 13 Sep 2022 15:10:51 +0000 (17:10 +0200)
Fixes #4990

com.woltlab.wcf/templates/headIncludeJavaScript.tpl
ts/WoltLabSuite/Core/Ui/Page/Menu/Main.ts
wcfsetup/install/files/acp/templates/header.tpl
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/Main.js
wcfsetup/install/files/style/ui/pageMenu.scss

index 4ff9f992da72b01f5e894664c315b4989b7848d6..d44ecb415796067a296093f8e1a1777eb0ee1849 100644 (file)
@@ -158,7 +158,8 @@ window.addEventListener('pageshow', function(event) {
                        'wcf.global.rss.copy.success': '{jslang}wcf.global.rss.copy.success{/jslang}',
                        'wcf.global.rss.accessToken.info': '{jslang}wcf.global.rss.accessToken.info{/jslang}',
                        'wcf.global.rss.withoutAccessToken': '{jslang}wcf.global.rss.withoutAccessToken{/jslang}',
-                       'wcf.global.rss.withAccessToken': '{jslang}wcf.global.rss.withAccessToken{/jslang}'
+                       'wcf.global.rss.withAccessToken': '{jslang}wcf.global.rss.withAccessToken{/jslang}',
+                       'wcf.user.language': '{jslang}wcf.user.language{/jslang}'
                        {if MODULE_LIKE}
                                ,'wcf.like.button.like': '{jslang}wcf.like.button.like{/jslang}',
                                'wcf.like.button.dislike': '{jslang}wcf.like.button.dislike{/jslang}',
index 91178d4e975e58f3852c4806145d4abd6682b0f8..e4b0d6a08bca0085f56de564114f1cbc63e9c55e 100644 (file)
@@ -12,6 +12,7 @@ import { PageMenuProvider } from "./Provider";
 import * as Language from "../../../Language";
 import DomUtil from "../../../Dom/Util";
 import { MenuItem, PageMenuMainProvider } from "./Main/Provider";
+import * as DropDownSimple from "../../Dropdown/Simple";
 
 type CallbackOpen = (event: MouseEvent) => void;
 
@@ -80,6 +81,11 @@ export class PageMenuMain implements PageMenuProvider {
 
     container.append(this.buildMainMenu());
 
+    const languageMenu = this.buildLanguageMenu();
+    if (languageMenu) {
+      container.append(languageMenu);
+    }
+
     const footerMenu = this.buildFooterMenu();
     if (footerMenu) {
       container.append(footerMenu);
@@ -155,6 +161,60 @@ export class PageMenuMain implements PageMenuProvider {
     }
   }
 
+  private buildLanguageMenu(): HTMLElement | null {
+    const dropDownMenu = DropDownSimple.getDropdownMenu("pageLanguageContainer");
+    if (dropDownMenu === undefined) {
+      return null;
+    }
+
+    const children: MenuItem[] = [];
+    const languageMapping = new Map<string, HTMLAnchorElement>();
+    Array.from(dropDownMenu.children).forEach((listItem: HTMLElement) => {
+      const identifier = listItem.dataset.languageCode!;
+      const title = listItem.querySelector("span")!.textContent!.trim();
+
+      languageMapping.set(identifier, listItem.querySelector("a")!);
+
+      children.push({
+        active: false,
+        children: [],
+        counter: 0,
+        depth: 1,
+        identifier,
+        title,
+      });
+    });
+
+    const menuItems: MenuItem[] = [
+      {
+        active: false,
+        children,
+        counter: 0,
+        depth: 0,
+        identifier: null,
+        title: Language.get("wcf.user.language"),
+      },
+    ];
+
+    const nav = document.createElement("nav");
+    nav.classList.add("pageMenuMainNavigation", "pageMenuMainNavigationLanguage");
+    nav.append(this.buildMenuItemList(menuItems, true));
+
+    // Forward clicks on the language to the actual language picker element.
+    nav
+      .querySelectorAll(".pageMenuMainItemList .pageMenuMainItemLabel[data-identifier]")
+      .forEach((element: HTMLAnchorElement) => {
+        element.addEventListener("click", (event) => {
+          event.preventDefault();
+
+          const identifier = element.dataset.identifier!;
+          languageMapping.get(identifier)!.click();
+        });
+      });
+
+    return nav;
+  }
+
   private buildFooterMenu(): HTMLElement | null {
     const box = document.querySelector('.box[data-box-identifier="com.woltlab.wcf.FooterMenu"]');
     if (box === null) {
@@ -176,32 +236,32 @@ export class PageMenuMain implements PageMenuProvider {
 
     const nav = document.createElement("nav");
     nav.classList.add("pageMenuMainNavigation");
-    nav.append(this.buildMenuItemList(menuItems));
+    nav.append(this.buildMenuItemList(menuItems, false));
 
     return nav;
   }
 
-  private buildMenuItemList(menuItems: MenuItem[]): HTMLUListElement {
+  private buildMenuItemList(menuItems: MenuItem[], isLanguageSelection: boolean): HTMLUListElement {
     const list = document.createElement("ul");
     list.classList.add("pageMenuMainItemList");
 
     menuItems
       .filter((menuItem) => {
         // Remove links that have no target (`#`) and do not contain any children.
-        if (!menuItem.link && menuItem.children.length === 0) {
+        if (!isLanguageSelection && !menuItem.link && menuItem.children.length === 0) {
           return false;
         }
 
         return true;
       })
       .forEach((menuItem) => {
-        list.append(this.buildMenuItem(menuItem));
+        list.append(this.buildMenuItem(menuItem, isLanguageSelection));
       });
 
     return list;
   }
 
-  private buildMenuItem(menuItem: MenuItem): HTMLLIElement {
+  private buildMenuItem(menuItem: MenuItem, isLanguageSelection: boolean): HTMLLIElement {
     const listItem = document.createElement("li");
     listItem.dataset.depth = menuItem.depth.toString();
     listItem.classList.add("pageMenuMainItem");
@@ -234,15 +294,22 @@ export class PageMenuMain implements PageMenuProvider {
       label.classList.add("pageMenuMainItemLabel");
       label.href = "#";
       label.textContent = menuItem.title;
-      label.addEventListener("click", (event) => {
-        event.preventDefault();
 
-        const button = label.nextElementSibling as HTMLAnchorElement;
-        button.click();
-      });
+      if (!isLanguageSelection || !menuItem.identifier) {
+        label.addEventListener("click", (event) => {
+          event.preventDefault();
+
+          const button = label.nextElementSibling as HTMLAnchorElement;
+          button.click();
+        });
 
-      // The button to expand the link group is used instead.
-      label.setAttribute("aria-hidden", "true");
+        // The button to expand the link group is used instead.
+        label.setAttribute("aria-hidden", "true");
+      }
+
+      if (isLanguageSelection && menuItem.identifier) {
+        label.dataset.identifier = menuItem.identifier;
+      }
 
       listItem.append(label);
     }
@@ -266,7 +333,7 @@ export class PageMenuMain implements PageMenuProvider {
       }
       button.setAttribute("aria-label", ariaLabel);
 
-      const list = this.buildMenuItemList(menuItem.children);
+      const list = this.buildMenuItemList(menuItem.children, isLanguageSelection);
       list.id = menuId;
       list.hidden = true;
 
index 1ddb07ea7da48f0b9d88d443f5698f4cfed33066..13ab2be356da64dc42592c9442fbfd9c5043f541 100644 (file)
                                'wcf.date.datePicker.hour': '{jslang}wcf.date.datePicker.hour{/jslang}',
                                'wcf.date.datePicker.minute': '{jslang}wcf.date.datePicker.minute{/jslang}',
                                'wcf.global.form.password.button.hide': '{jslang}wcf.global.form.password.button.hide{/jslang}',
-                               'wcf.global.form.password.button.show': '{jslang}wcf.global.form.password.button.show{/jslang}'
+                               'wcf.global.form.password.button.show': '{jslang}wcf.global.form.password.button.show{/jslang}',
+                               'wcf.user.language': '{jslang}wcf.user.language{/jslang}'
                                {event name='javascriptLanguageImport'}
                        });
                        
index a803706fece850cfb409a55abe3a5d9f9055661c..94a394ee69ece6f4c1ca913cd71543550224d4e6 100644 (file)
@@ -6,13 +6,14 @@
  * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module WoltLabSuite/Core/Ui/Page/Menu/Main
  */
-define(["require", "exports", "tslib", "./Container", "../../../Language", "../../../Dom/Util"], function (require, exports, tslib_1, Container_1, Language, Util_1) {
+define(["require", "exports", "tslib", "./Container", "../../../Language", "../../../Dom/Util", "../../Dropdown/Simple"], function (require, exports, tslib_1, Container_1, Language, Util_1, DropDownSimple) {
     "use strict";
     Object.defineProperty(exports, "__esModule", { value: true });
     exports.PageMenuMain = void 0;
     Container_1 = tslib_1.__importDefault(Container_1);
     Language = tslib_1.__importStar(Language);
     Util_1 = tslib_1.__importDefault(Util_1);
+    DropDownSimple = tslib_1.__importStar(DropDownSimple);
     class PageMenuMain {
         constructor(menuItemProvider) {
             this.menuItemBadges = new Map();
@@ -58,6 +59,10 @@ define(["require", "exports", "tslib", "./Container", "../../../Language", "../.
             container.classList.add("pageMenuMainContainer");
             container.addEventListener("scroll", () => this.updateOverflowIndicator(container), { passive: true });
             container.append(this.buildMainMenu());
+            const languageMenu = this.buildLanguageMenu();
+            if (languageMenu) {
+                container.append(languageMenu);
+            }
             const footerMenu = this.buildFooterMenu();
             if (footerMenu) {
                 container.append(footerMenu);
@@ -116,6 +121,51 @@ define(["require", "exports", "tslib", "./Container", "../../../Language", "../.
                 }
             }
         }
+        buildLanguageMenu() {
+            const dropDownMenu = DropDownSimple.getDropdownMenu("pageLanguageContainer");
+            if (dropDownMenu === undefined) {
+                return null;
+            }
+            const children = [];
+            const languageMapping = new Map();
+            Array.from(dropDownMenu.children).forEach((listItem) => {
+                const identifier = listItem.dataset.languageCode;
+                const title = listItem.querySelector("span").textContent.trim();
+                languageMapping.set(identifier, listItem.querySelector("a"));
+                children.push({
+                    active: false,
+                    children: [],
+                    counter: 0,
+                    depth: 1,
+                    identifier,
+                    title,
+                });
+            });
+            const menuItems = [
+                {
+                    active: false,
+                    children,
+                    counter: 0,
+                    depth: 0,
+                    identifier: null,
+                    title: Language.get("wcf.user.language"),
+                },
+            ];
+            const nav = document.createElement("nav");
+            nav.classList.add("pageMenuMainNavigation", "pageMenuMainNavigationLanguage");
+            nav.append(this.buildMenuItemList(menuItems, true));
+            // Forward clicks on the language to the actual language picker element.
+            nav
+                .querySelectorAll(".pageMenuMainItemList .pageMenuMainItemLabel[data-identifier]")
+                .forEach((element) => {
+                element.addEventListener("click", (event) => {
+                    event.preventDefault();
+                    const identifier = element.dataset.identifier;
+                    languageMapping.get(identifier).click();
+                });
+            });
+            return nav;
+        }
         buildFooterMenu() {
             const box = document.querySelector('.box[data-box-identifier="com.woltlab.wcf.FooterMenu"]');
             if (box === null) {
@@ -132,26 +182,26 @@ define(["require", "exports", "tslib", "./Container", "../../../Language", "../.
             const menuItems = this.menuItemProvider.getMenuItems(boxMenu);
             const nav = document.createElement("nav");
             nav.classList.add("pageMenuMainNavigation");
-            nav.append(this.buildMenuItemList(menuItems));
+            nav.append(this.buildMenuItemList(menuItems, false));
             return nav;
         }
-        buildMenuItemList(menuItems) {
+        buildMenuItemList(menuItems, isLanguageSelection) {
             const list = document.createElement("ul");
             list.classList.add("pageMenuMainItemList");
             menuItems
                 .filter((menuItem) => {
                 // Remove links that have no target (`#`) and do not contain any children.
-                if (!menuItem.link && menuItem.children.length === 0) {
+                if (!isLanguageSelection && !menuItem.link && menuItem.children.length === 0) {
                     return false;
                 }
                 return true;
             })
                 .forEach((menuItem) => {
-                list.append(this.buildMenuItem(menuItem));
+                list.append(this.buildMenuItem(menuItem, isLanguageSelection));
             });
             return list;
         }
-        buildMenuItem(menuItem) {
+        buildMenuItem(menuItem, isLanguageSelection) {
             const listItem = document.createElement("li");
             listItem.dataset.depth = menuItem.depth.toString();
             listItem.classList.add("pageMenuMainItem");
@@ -180,13 +230,18 @@ define(["require", "exports", "tslib", "./Container", "../../../Language", "../.
                 label.classList.add("pageMenuMainItemLabel");
                 label.href = "#";
                 label.textContent = menuItem.title;
-                label.addEventListener("click", (event) => {
-                    event.preventDefault();
-                    const button = label.nextElementSibling;
-                    button.click();
-                });
-                // The button to expand the link group is used instead.
-                label.setAttribute("aria-hidden", "true");
+                if (!isLanguageSelection || !menuItem.identifier) {
+                    label.addEventListener("click", (event) => {
+                        event.preventDefault();
+                        const button = label.nextElementSibling;
+                        button.click();
+                    });
+                    // The button to expand the link group is used instead.
+                    label.setAttribute("aria-hidden", "true");
+                }
+                if (isLanguageSelection && menuItem.identifier) {
+                    label.dataset.identifier = menuItem.identifier;
+                }
                 listItem.append(label);
             }
             if (menuItem.children.length) {
@@ -204,7 +259,7 @@ define(["require", "exports", "tslib", "./Container", "../../../Language", "../.
                     ariaLabel = Language.get("wcf.menu.page.button.toggle", { title: menuItem.title });
                 }
                 button.setAttribute("aria-label", ariaLabel);
-                const list = this.buildMenuItemList(menuItem.children);
+                const list = this.buildMenuItemList(menuItem.children, isLanguageSelection);
                 list.id = menuId;
                 list.hidden = true;
                 button.addEventListener("click", (event) => {
index 5c7fd49e47a0944b7de850b8ac62a9d6cf56c9c3..1d8c4469c0e3107e016c558fc09ddc45a51d3d4d 100644 (file)
@@ -55,6 +55,7 @@
        }
 }
 
+.pageMenuMainNavigationLanguage,
 .pageMenuMainNavigationFooter {
        /* The footer is placed at the very bottom of the main menu which
           is accomplished by setting `margin-top: auto`. However, this
        margin-top: auto;
 }
 
+.pageMenuMainNavigationLanguage + .pageMenuMainNavigationFooter {
+       padding-top: 0;
+       margin-top: 0;
+}
+
 .pageMenuMainItem {
        border-bottom: 1px solid var(--border-color);
        column-gap: 10px;