Resolved a few compatibility issues
authorAlexander Ebert <ebert@woltlab.com>
Fri, 27 Nov 2020 18:51:14 +0000 (19:51 +0100)
committerAlexander Ebert <ebert@woltlab.com>
Fri, 27 Nov 2020 18:51:14 +0000 (19:51 +0100)
The internal ids must remain strings, because the legacy `Dictionary` implementation relied on a plain object. In JavaScript objects, all keys are implicitly converted to strings, but Map treats `1` and `"1"` to be different.

wcfsetup/install/files/js/WoltLabSuite/Core/StringUtil.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Message/InlineEditor.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Message/Manager.js
wcfsetup/install/files/ts/WoltLabSuite/Core/StringUtil.ts
wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Message/InlineEditor.ts
wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Message/Manager.ts

index a940cc2b6eb0dd5e273e112d8109e71d2f3efc31..28e090fadb148d9f64ffb267443ea0e1c2687a91 100644 (file)
@@ -10,7 +10,7 @@
 define(["require", "exports", "tslib", "./NumberUtil"], function (require, exports, tslib_1, NumberUtil) {
     "use strict";
     Object.defineProperty(exports, "__esModule", { value: true });
-    exports.setupI18n = exports.shortUnit = exports.unescapeHTML = exports.ucfirst = exports.lcfirst = exports.formatNumeric = exports.escapeRegExp = exports.escapeHTML = exports.addThousandsSeparator = void 0;
+    exports.setupI18n = exports.toCamelCase = exports.shortUnit = exports.unescapeHTML = exports.ucfirst = exports.lcfirst = exports.formatNumeric = exports.escapeRegExp = exports.escapeHTML = exports.addThousandsSeparator = void 0;
     NumberUtil = tslib_1.__importStar(NumberUtil);
     let _decimalPoint = ".";
     let _thousandsSeparator = ",";
@@ -106,6 +106,25 @@ define(["require", "exports", "tslib", "./NumberUtil"], function (require, expor
         return formatNumeric(number) + unitSuffix;
     }
     exports.shortUnit = shortUnit;
+    /**
+     * Converts a lower-case string containing dashed to camelCase for use
+     * with the `dataset` property.
+     */
+    function toCamelCase(value) {
+        if (!value.includes("-")) {
+            return value;
+        }
+        return value
+            .split("-")
+            .map((part, index) => {
+            if (index > 0) {
+                part = ucfirst(part);
+            }
+            return part;
+        })
+            .join("");
+    }
+    exports.toCamelCase = toCamelCase;
     function setupI18n(values) {
         _decimalPoint = values.decimalPoint;
         _thousandsSeparator = values.thousandsSeparator;
index feae49bcec09a9d17da319819d512170a058569a..45d9899da371feff76b18a288f2c2412551b3dcb 100644 (file)
@@ -509,7 +509,7 @@ define(["require", "exports", "tslib", "../../Ajax", "../../Core", "../../Dom/Ch
          * Returns the element's `data-object-id` value.
          */
         _getObjectId(element) {
-            return ~~(element.dataset.objectId || "");
+            return element.dataset.objectId || "";
         }
         _ajaxFailure(data) {
             const elementData = this._elements.get(this._activeElement);
index ef6e6f0ac801c4c11b1c991fad65aecb3fbb800c..ca46eb1049c0b3b7187ea3e474e3bcac98643b7e 100644 (file)
@@ -6,12 +6,13 @@
  * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module  WoltLabSuite/Core/Ui/Message/Manager
  */
-define(["require", "exports", "tslib", "../../Ajax", "../../Core", "../../Dom/Change/Listener", "../../Language"], function (require, exports, tslib_1, Ajax, Core, Listener_1, Language) {
+define(["require", "exports", "tslib", "../../Ajax", "../../Core", "../../Dom/Change/Listener", "../../Language", "../../StringUtil"], function (require, exports, tslib_1, Ajax, Core, Listener_1, Language, StringUtil) {
     "use strict";
     Ajax = tslib_1.__importStar(Ajax);
     Core = tslib_1.__importStar(Core);
     Listener_1 = tslib_1.__importDefault(Listener_1);
     Language = tslib_1.__importStar(Language);
+    StringUtil = tslib_1.__importStar(StringUtil);
     class UiMessageManager {
         /**
          * Initializes a new manager instance.
@@ -32,8 +33,7 @@ define(["require", "exports", "tslib", "../../Ajax", "../../Core", "../../Dom/Ch
         rebuild() {
             this._elements.clear();
             document.querySelectorAll(this._options.selector).forEach((element) => {
-                const objectId = ~~(element.dataset.objectId || "0");
-                this._elements.set(objectId, element);
+                this._elements.set(element.dataset.objectId, element);
             });
         }
         /**
@@ -41,7 +41,7 @@ define(["require", "exports", "tslib", "../../Ajax", "../../Core", "../../Dom/Ch
          * with "can" or "can-" as this is automatically assumed by this method.
          */
         getPermission(objectId, permission) {
-            permission = "can-" + this._getAttributeName(permission);
+            permission = "can" + StringUtil.ucfirst(permission);
             const element = this._elements.get(objectId);
             if (element === undefined) {
                 throw new Error(`Unknown object id '${objectId}' for selector '${this._options.selector}'`);
@@ -56,8 +56,7 @@ define(["require", "exports", "tslib", "../../Ajax", "../../Core", "../../Dom/Ch
             if (element === undefined) {
                 throw new Error(`Unknown object id '${objectId}' for selector '${this._options.selector}'`);
             }
-            const attributeName = this._getAttributeName(propertyName);
-            const value = element.dataset[attributeName] || "";
+            const value = element.dataset[StringUtil.toCamelCase(propertyName)] || "";
             if (asBool) {
                 return Core.stringToBool(value);
             }
@@ -125,8 +124,7 @@ define(["require", "exports", "tslib", "../../Ajax", "../../Core", "../../Dom/Ch
          * Updates a single property of a message element.
          */
         _update(element, propertyName, propertyValue) {
-            const attributeName = this._getAttributeName(propertyName);
-            element.dataset[attributeName] = propertyValue.toString();
+            element.dataset[propertyName] = propertyValue.toString();
             // handle special properties
             const propertyValueBoolean = propertyValue == 1 || propertyValue === true || propertyValue === "true";
             this._updateState(element, propertyName, propertyValue, propertyValueBoolean);
@@ -194,6 +192,8 @@ define(["require", "exports", "tslib", "../../Ajax", "../../Core", "../../Dom/Ch
         }
         /**
          * Transforms camel-cased property names into their attribute equivalent.
+         *
+         * @deprecated 5.4 Access the value via `element.dataset` which uses camel-case.
          */
         _getAttributeName(propertyName) {
             if (propertyName.indexOf("-") !== -1) {
index 407c5d60cdf484e271465e4c3f331606ee0f574b..9c7c3b438bf93532d0b63c26af0daf1890c4e974 100644 (file)
@@ -111,6 +111,27 @@ export function shortUnit(number: number): string {
   return formatNumeric(number) + unitSuffix;
 }
 
+/**
+ * Converts a lower-case string containing dashed to camelCase for use
+ * with the `dataset` property.
+ */
+export function toCamelCase(value: string): string {
+  if (!value.includes("-")) {
+    return value;
+  }
+
+  return value
+    .split("-")
+    .map((part, index) => {
+      if (index > 0) {
+        part = ucfirst(part);
+      }
+
+      return part;
+    })
+    .join("");
+}
+
 interface I18nValues {
   decimalPoint: string;
   thousandsSeparator: string;
index 0dd0e8ee22d657ec0185d49ae265cbd3f683cb89..90d0e373043dd54d653dd7598f6b48738fbc39ce 100644 (file)
@@ -24,7 +24,7 @@ interface MessageInlineEditorOptions {
   canEditInline: boolean;
 
   className: string;
-  containerId: number;
+  containerId: string;
   dropdownIdentifier: string;
   editorPrefix: string;
 
@@ -656,7 +656,7 @@ class UiMessageInlineEditor implements AjaxCallbackObject {
   /**
    * Returns the hash added to the url after successfully editing a message.
    */
-  protected _getHash(objectId: number): string {
+  protected _getHash(objectId: string): string {
     return `#message${objectId}`;
   }
 
@@ -678,8 +678,8 @@ class UiMessageInlineEditor implements AjaxCallbackObject {
   /**
    * Returns the element's `data-object-id` value.
    */
-  protected _getObjectId(element: HTMLElement): number {
-    return ~~(element.dataset.objectId || "");
+  protected _getObjectId(element: HTMLElement): string {
+    return element.dataset.objectId || "";
   }
 
   _ajaxFailure(data: ResponseData): boolean {
index 5d38424c1982fa897f4e0961aac5a79309c41816..f4403ef622771e4fc82b6a675fb349aa8d414fab 100644 (file)
@@ -12,6 +12,7 @@ import { AjaxCallbackObject, AjaxCallbackSetup, ResponseData } from "../../Ajax/
 import * as Core from "../../Core";
 import DomChangeListener from "../../Dom/Change/Listener";
 import * as Language from "../../Language";
+import * as StringUtil from "../../StringUtil";
 
 interface MessageManagerOptions {
   className: string;
@@ -21,7 +22,7 @@ interface MessageManagerOptions {
 type StringableValue = boolean | number | string;
 
 class UiMessageManager implements AjaxCallbackObject {
-  protected readonly _elements = new Map<number, HTMLElement>();
+  protected readonly _elements = new Map<string, HTMLElement>();
   protected readonly _options: MessageManagerOptions;
 
   /**
@@ -49,8 +50,7 @@ class UiMessageManager implements AjaxCallbackObject {
     this._elements.clear();
 
     document.querySelectorAll(this._options.selector).forEach((element: HTMLElement) => {
-      const objectId = ~~(element.dataset.objectId || "0");
-      this._elements.set(objectId, element);
+      this._elements.set(element.dataset.objectId!, element);
     });
   }
 
@@ -58,8 +58,8 @@ class UiMessageManager implements AjaxCallbackObject {
    * Returns a boolean value for the given permission. The permission should not start
    * with "can" or "can-" as this is automatically assumed by this method.
    */
-  getPermission(objectId: number, permission: string): boolean {
-    permission = "can-" + this._getAttributeName(permission);
+  getPermission(objectId: string, permission: string): boolean {
+    permission = "can" + StringUtil.ucfirst(permission);
     const element = this._elements.get(objectId);
     if (element === undefined) {
       throw new Error(`Unknown object id '${objectId}' for selector '${this._options.selector}'`);
@@ -71,14 +71,13 @@ class UiMessageManager implements AjaxCallbackObject {
   /**
    * Returns the given property value from a message, optionally supporting a boolean return value.
    */
-  getPropertyValue(objectId: number, propertyName: string, asBool: boolean): boolean | string {
+  getPropertyValue(objectId: string, propertyName: string, asBool: boolean): boolean | string {
     const element = this._elements.get(objectId);
     if (element === undefined) {
       throw new Error(`Unknown object id '${objectId}' for selector '${this._options.selector}'`);
     }
 
-    const attributeName = this._getAttributeName(propertyName);
-    const value = element.dataset[attributeName] || "";
+    const value = element.dataset[StringUtil.toCamelCase(propertyName)] || "";
 
     if (asBool) {
       return Core.stringToBool(value);
@@ -90,7 +89,7 @@ class UiMessageManager implements AjaxCallbackObject {
   /**
    * Invokes a method for given message object id in order to alter its state or properties.
    */
-  update(objectId: number, actionName: string, parameters?: ArbitraryObject): void {
+  update(objectId: string, actionName: string, parameters?: ArbitraryObject): void {
     Ajax.api(this, {
       actionName: actionName,
       parameters: parameters || {},
@@ -103,7 +102,7 @@ class UiMessageManager implements AjaxCallbackObject {
    * not support setting individual properties per message, instead all property changes
    * are applied to all matching message objects.
    */
-  updateItems(objectIds: number | number[], data: ArbitraryObject): void {
+  updateItems(objectIds: string | string[], data: ArbitraryObject): void {
     if (!Array.isArray(objectIds)) {
       objectIds = [objectIds];
     }
@@ -132,7 +131,7 @@ class UiMessageManager implements AjaxCallbackObject {
   /**
    * Sets or removes a message note identified by its unique CSS class.
    */
-  setNote(objectId: number, className: string, htmlContent: string): void {
+  setNote(objectId: string, className: string, htmlContent: string): void {
     const element = this._elements.get(objectId);
     if (element === undefined) {
       throw new Error(`Unknown object id '${objectId}' for selector '${this._options.selector}'`);
@@ -158,8 +157,7 @@ class UiMessageManager implements AjaxCallbackObject {
    * Updates a single property of a message element.
    */
   protected _update(element: HTMLElement, propertyName: string, propertyValue: StringableValue): void {
-    const attributeName = this._getAttributeName(propertyName);
-    element.dataset[attributeName] = propertyValue.toString();
+    element.dataset[propertyName] = propertyValue.toString();
 
     // handle special properties
     const propertyValueBoolean = propertyValue == 1 || propertyValue === true || propertyValue === "true";
@@ -255,6 +253,8 @@ class UiMessageManager implements AjaxCallbackObject {
 
   /**
    * Transforms camel-cased property names into their attribute equivalent.
+   *
+   * @deprecated 5.4 Access the value via `element.dataset` which uses camel-case.
    */
   protected _getAttributeName(propertyName: string): string {
     if (propertyName.indexOf("-") !== -1) {