Converted additional modules to TypeScript
authorAlexander Ebert <ebert@woltlab.com>
Thu, 15 Oct 2020 23:13:13 +0000 (01:13 +0200)
committerTim Düsterhus <duesterhus@woltlab.com>
Wed, 28 Oct 2020 11:23:28 +0000 (12:23 +0100)
32 files changed:
global.d.ts
wcfsetup/install/files/js/WoltLabSuite/Core/ColorUtil.js
wcfsetup/install/files/js/WoltLabSuite/Core/Core.js
wcfsetup/install/files/js/WoltLabSuite/Core/Devtools.js
wcfsetup/install/files/js/WoltLabSuite/Core/Dictionary.js
wcfsetup/install/files/js/WoltLabSuite/Core/Environment.js
wcfsetup/install/files/js/WoltLabSuite/Core/FileUtil.js
wcfsetup/install/files/js/WoltLabSuite/Core/I18n/Plural.js
wcfsetup/install/files/js/WoltLabSuite/Core/Language.js
wcfsetup/install/files/js/WoltLabSuite/Core/List.js
wcfsetup/install/files/js/WoltLabSuite/Core/NumberUtil.js
wcfsetup/install/files/js/WoltLabSuite/Core/ObjectMap.js
wcfsetup/install/files/js/WoltLabSuite/Core/Permission.js
wcfsetup/install/files/js/WoltLabSuite/Core/StringUtil.js
wcfsetup/install/files/js/WoltLabSuite/Core/Template.js
wcfsetup/install/files/js/WoltLabSuite/Core/User.js
wcfsetup/install/files/ts/WoltLabSuite/Core/ColorUtil.ts [new file with mode: 0644]
wcfsetup/install/files/ts/WoltLabSuite/Core/Core.ts
wcfsetup/install/files/ts/WoltLabSuite/Core/Devtools.ts [new file with mode: 0644]
wcfsetup/install/files/ts/WoltLabSuite/Core/Dictionary.ts
wcfsetup/install/files/ts/WoltLabSuite/Core/Environment.ts [new file with mode: 0644]
wcfsetup/install/files/ts/WoltLabSuite/Core/FileUtil.ts [new file with mode: 0644]
wcfsetup/install/files/ts/WoltLabSuite/Core/I18n/Plural.ts [new file with mode: 0644]
wcfsetup/install/files/ts/WoltLabSuite/Core/Language.ts [new file with mode: 0644]
wcfsetup/install/files/ts/WoltLabSuite/Core/List.ts [new file with mode: 0644]
wcfsetup/install/files/ts/WoltLabSuite/Core/NumberUtil.ts [new file with mode: 0644]
wcfsetup/install/files/ts/WoltLabSuite/Core/ObjectMap.ts [new file with mode: 0644]
wcfsetup/install/files/ts/WoltLabSuite/Core/Permission.ts [new file with mode: 0644]
wcfsetup/install/files/ts/WoltLabSuite/Core/StringUtil.ts [new file with mode: 0644]
wcfsetup/install/files/ts/WoltLabSuite/Core/Template.grammar.d.ts [new file with mode: 0644]
wcfsetup/install/files/ts/WoltLabSuite/Core/Template.ts [new file with mode: 0644]
wcfsetup/install/files/ts/WoltLabSuite/Core/User.ts [new file with mode: 0644]

index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..8e4ad4aef56b91aae6d6c6be4e8b4e84423bf488 100644 (file)
@@ -0,0 +1,15 @@
+import Devtools from './wcfsetup/install/files/ts/WoltLabSuite/Core/Devtools';
+import ColorUtil from './wcfsetup/install/files/ts/WoltLabSuite/Core/ColorUtil';
+
+declare global {
+  interface Window {
+    Devtools?: typeof Devtools;
+    WCF_PATH: string;
+
+    __wcf_bc_colorUtil: typeof ColorUtil;
+  }
+
+  interface String {
+    hashCode: () => string;
+  }
+}
index 83130d1dc7a473be6246653c9b10219421140624..ae3ae0ce476b55c3588c2f31c87616a8046ce779 100644 (file)
  * Helper functions to convert between different color formats.
  *
  * @author      Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
+ * @copyright  2001-2019 WoltLab GmbH
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     ColorUtil (alias)
+ * @module  ColorUtil (alias)
  * @module      WoltLabSuite/Core/ColorUtil
  */
-define([], function () {
-       "use strict";
-       
-       /**
-        * @exports     WoltLabSuite/Core/ColorUtil
-        */
-       var ColorUtil = {
-               /**
-                * Converts a HSV color into RGB.
-                *
-                * @see https://secure.wikimedia.org/wikipedia/de/wiki/HSV-Farbraum#Transformation_von_RGB_und_HSV
-                *
-                * @param       {int}           h
-                * @param       {int}           s
-                * @param       {int}           v
-                * @return      {Object}
-                */
-               hsvToRgb: function(h, s, v) {
-                       var rgb = { r: 0, g: 0, b: 0 };
-                       var h2, f, p, q, t;
-                       
-                       h2 = Math.floor(h / 60);
-                       f = h / 60 - h2;
-                       
-                       s /= 100;
-                       v /= 100;
-                       
-                       p = v * (1 - s);
-                       q = v * (1 - s * f);
-                       t = v * (1 - s * (1 - f));
-                       
-                       if (s == 0) {
-                               rgb.r = rgb.g = rgb.b = v;
-                       }
-                       else {
-                               switch (h2) {
-                                       case 1:
-                                               rgb.r = q;
-                                               rgb.g = v;
-                                               rgb.b = p;
-                                               break;
-                                       
-                                       case 2:
-                                               rgb.r = p;
-                                               rgb.g = v;
-                                               rgb.b = t;
-                                               break;
-                                       
-                                       case 3:
-                                               rgb.r = p;
-                                               rgb.g = q;
-                                               rgb.b = v;
-                                               break;
-                                       
-                                       case 4:
-                                               rgb.r = t;
-                                               rgb.g = p;
-                                               rgb.b = v;
-                                               break;
-                                       
-                                       case 5:
-                                               rgb.r = v;
-                                               rgb.g = p;
-                                               rgb.b = q;
-                                               break;
-                                       
-                                       case 0:
-                                       case 6:
-                                               rgb.r = v;
-                                               rgb.g = t;
-                                               rgb.b = p;
-                                               break;
-                               }
-                       }
-                       
-                       return {
-                               r: Math.round(rgb.r * 255),
-                               g: Math.round(rgb.g * 255),
-                               b: Math.round(rgb.b * 255)
-                       };
-               },
-               
-               /**
-                * Converts a RGB color into HSV.
-                *
-                * @see https://secure.wikimedia.org/wikipedia/de/wiki/HSV-Farbraum#Transformation_von_RGB_und_HSV
-                *
-                * @param       {int}           r
-                * @param       {int}           g
-                * @param       {int}           b
-                * @return      {Object}
-                */
-               rgbToHsv: function(r, g, b) {
-                       var h, s, v;
-                       var max, min, diff;
-                       
-                       r /= 255;
-                       g /= 255;
-                       b /= 255;
-                       
-                       max = Math.max(Math.max(r, g), b);
-                       min = Math.min(Math.min(r, g), b);
-                       diff = max - min;
-                       
-                       h = 0;
-                       if (max !== min) {
-                               switch (max) {
-                                       case r:
-                                               h = 60 * ((g - b) / diff);
-                                               break;
-                                       
-                                       case g:
-                                               h = 60 * (2 + (b - r) / diff);
-                                               break;
-                                       
-                                       case b:
-                                               h = 60 * (4 + (r - g) / diff);
-                                               break;
-                               }
-                               
-                               if (h < 0) {
-                                       h += 360;
-                               }
-                       }
-                       
-                       if (max === 0) {
-                               s = 0;
-                       }
-                       else {
-                               s = diff / max;
-                       }
-                       
-                       v = max;
-                       
-                       return {
-                               h: Math.round(h),
-                               s: Math.round(s * 100),
-                               v: Math.round(v * 100)
-                       };
-               },
-               
-               /**
-                * Converts HEX into RGB.
-                *
-                * @param       {string}        hex
-                * @return      {Object}
-                */
-               hexToRgb: function(hex) {
-                       if (/^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(hex)) {
-                               // only convert #abc and #abcdef
-                               var parts = hex.split('');
-                               
-                               // drop the hashtag
-                               if (parts[0] === '#') {
-                                       parts.shift();
-                               }
-                               
-                               // parse shorthand #xyz
-                               if (parts.length === 3) {
-                                       return {
-                                               r: parseInt(parts[0] + '' + parts[0], 16),
-                                               g: parseInt(parts[1] + '' + parts[1], 16),
-                                               b: parseInt(parts[2] + '' + parts[2], 16)
-                                       };
-                               }
-                               else {
-                                       return {
-                                               r: parseInt(parts[0] + '' + parts[1], 16),
-                                               g: parseInt(parts[2] + '' + parts[3], 16),
-                                               b: parseInt(parts[4] + '' + parts[5], 16)
-                                       };
-                               }
-                       }
-                       
-                       return Number.NaN;
-               },
-               
-               /**
-                * Converts a RGB into HEX.
-                *
-                * @see http://www.linuxtopia.org/online_books/javascript_guides/javascript_faq/rgbtohex.htm
-                *
-                * @param       {int}           r
-                * @param       {int}           g
-                * @param       {int}           b
-                * @return      {string}
-                */
-               rgbToHex: function(r, g, b) {
-                       var charList = "0123456789ABCDEF";
-                       
-                       if (g === undefined) {
-                               if (r.toString().match(/^rgba?\((\d+), ?(\d+), ?(\d+)(?:, ?[0-9.]+)?\)$/)) {
-                                       r = RegExp.$1;
-                                       g = RegExp.$2;
-                                       b = RegExp.$3;
-                               }
-                       }
-                       
-                       return (charList.charAt((r - r % 16) / 16) + '' + charList.charAt(r % 16)) + '' + (charList.charAt((g - g % 16) / 16) + '' + charList.charAt(g % 16)) + '' + (charList.charAt((b - b % 16) / 16) + '' + charList.charAt(b % 16));
-               }
-       };
-       
-       // WCF.ColorPicker compatibility (color format conversion)
-       window.__wcf_bc_colorUtil = ColorUtil;
-       
-       return ColorUtil;
+define(["require", "exports"], function (require, exports) {
+    "use strict";
+    const ColorUtil = {
+        /**
+         * Converts a HSV color into RGB.
+         *
+         * @see  https://secure.wikimedia.org/wikipedia/de/wiki/HSV-Farbraum#Transformation_von_RGB_und_HSV
+         */
+        hsvToRgb: (h, s, v) => {
+            const rgb = { r: 0, g: 0, b: 0 };
+            let h2, f, p, q, t;
+            h2 = Math.floor(h / 60);
+            f = h / 60 - h2;
+            s /= 100;
+            v /= 100;
+            p = v * (1 - s);
+            q = v * (1 - s * f);
+            t = v * (1 - s * (1 - f));
+            if (s == 0) {
+                rgb.r = rgb.g = rgb.b = v;
+            }
+            else {
+                switch (h2) {
+                    case 1:
+                        rgb.r = q;
+                        rgb.g = v;
+                        rgb.b = p;
+                        break;
+                    case 2:
+                        rgb.r = p;
+                        rgb.g = v;
+                        rgb.b = t;
+                        break;
+                    case 3:
+                        rgb.r = p;
+                        rgb.g = q;
+                        rgb.b = v;
+                        break;
+                    case 4:
+                        rgb.r = t;
+                        rgb.g = p;
+                        rgb.b = v;
+                        break;
+                    case 5:
+                        rgb.r = v;
+                        rgb.g = p;
+                        rgb.b = q;
+                        break;
+                    case 0:
+                    case 6:
+                        rgb.r = v;
+                        rgb.g = t;
+                        rgb.b = p;
+                        break;
+                }
+            }
+            return {
+                r: Math.round(rgb.r * 255),
+                g: Math.round(rgb.g * 255),
+                b: Math.round(rgb.b * 255),
+            };
+        },
+        /**
+         * Converts a RGB color into HSV.
+         *
+         * @see  https://secure.wikimedia.org/wikipedia/de/wiki/HSV-Farbraum#Transformation_von_RGB_und_HSV
+         */
+        rgbToHsv: (r, g, b) => {
+            let h, s, v;
+            let max, min, diff;
+            r /= 255;
+            g /= 255;
+            b /= 255;
+            max = Math.max(Math.max(r, g), b);
+            min = Math.min(Math.min(r, g), b);
+            diff = max - min;
+            h = 0;
+            if (max !== min) {
+                switch (max) {
+                    case r:
+                        h = 60 * ((g - b) / diff);
+                        break;
+                    case g:
+                        h = 60 * (2 + (b - r) / diff);
+                        break;
+                    case b:
+                        h = 60 * (4 + (r - g) / diff);
+                        break;
+                }
+                if (h < 0) {
+                    h += 360;
+                }
+            }
+            if (max === 0) {
+                s = 0;
+            }
+            else {
+                s = diff / max;
+            }
+            v = max;
+            return {
+                h: Math.round(h),
+                s: Math.round(s * 100),
+                v: Math.round(v * 100),
+            };
+        },
+        /**
+         * Converts HEX into RGB.
+         */
+        hexToRgb: (hex) => {
+            if (/^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(hex)) {
+                // only convert #abc and #abcdef
+                const parts = hex.split('');
+                // drop the hashtag
+                if (parts[0] === '#') {
+                    parts.shift();
+                }
+                // parse shorthand #xyz
+                if (parts.length === 3) {
+                    return {
+                        r: parseInt(parts[0] + '' + parts[0], 16),
+                        g: parseInt(parts[1] + '' + parts[1], 16),
+                        b: parseInt(parts[2] + '' + parts[2], 16),
+                    };
+                }
+                else {
+                    return {
+                        r: parseInt(parts[0] + '' + parts[1], 16),
+                        g: parseInt(parts[2] + '' + parts[3], 16),
+                        b: parseInt(parts[4] + '' + parts[5], 16),
+                    };
+                }
+            }
+            return Number.NaN;
+        },
+        /**
+         * Converts a RGB into HEX.
+         *
+         * @see  http://www.linuxtopia.org/online_books/javascript_guides/javascript_faq/rgbtohex.htm
+         */
+        rgbToHex: (r, g, b) => {
+            const charList = '0123456789ABCDEF';
+            if (g === undefined) {
+                if (r.toString().match(/^rgba?\((\d+), ?(\d+), ?(\d+)(?:, ?[0-9.]+)?\)$/)) {
+                    r = +RegExp.$1;
+                    g = +RegExp.$2;
+                    b = +RegExp.$3;
+                }
+            }
+            return (charList.charAt((r - r % 16) / 16) + '' + charList.charAt(r % 16)) + '' + (charList.charAt((g - g % 16) / 16) + '' + charList.charAt(g % 16)) + '' + (charList.charAt((b - b % 16) / 16) + '' + charList.charAt(b % 16));
+        },
+    };
+    // WCF.ColorPicker compatibility (color format conversion)
+    window.__wcf_bc_colorUtil = ColorUtil;
+    return ColorUtil;
 });
index 17be029c996ce24d8e8b4a4a7f0f6565949dbcfe..25a9e821811f90a2e2bcde46b54067a0f32a67e4 100644 (file)
@@ -28,8 +28,7 @@ define(["require", "exports"], function (require, exports) {
         Object.keys(obj).forEach(key => newObj[key] = _clone(obj[key]));
         return newObj;
     };
-    //noinspection JSUnresolvedVariable
-    const _prefix = 'wsc1337' + /*window.WCF_PATH.hashCode()*/ +'-';
+    const _prefix = 'wsc' + window.WCF_PATH.hashCode() + '-';
     /**
      * Deep clones an object.
      */
index 16ff52aad09df15340ee8b14afc6af1755f9e154..1445574aa579cf636fc16650fab3b606833ef7bc 100644 (file)
 /**
  * Developer tools for WoltLab Suite.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     Devtools (alias)
- * @module     WoltLabSuite/Core/Devtools
+ *
+ * @author  Alexander Ebert
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module  Devtools (alias)
+ * @module  WoltLabSuite/Core/Devtools
  */
-define([], function() {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               return {
-                       help: function () {},
-                       toggleEditorAutosave: function () {},
-                       toggleEventLogging: function () {},
-                       _internal_: {
-                               enable: function () {},
-                               editorAutosave: function () {},
-                               eventLog: function() {}
-                       }
-               };
-       }
-       
-       var _settings = {
-               editorAutosave: true,
-               eventLogging: false
-       };
-       
-       var _updateConfig = function () {
-               if (window.sessionStorage) {
-                       window.sessionStorage.setItem("__wsc_devtools_config", JSON.stringify(_settings));
-               }
-       };
-       
-       var Devtools = {
-               /**
-                * Prints the list of available commands.
-                */
-               help: function () {
-                       window.console.log("");
-                       window.console.log("%cAvailable commands:", "text-decoration: underline");
-                       
-                       var cmds = [];
-                       for (var cmd in Devtools) {
-                               if (cmd !== '_internal_' && Devtools.hasOwnProperty(cmd)) {
-                                       cmds.push(cmd);
-                               }
-                       }
-                       cmds.sort().forEach(function(cmd) {
-                               window.console.log("\tDevtools." + cmd + "()");
-                       });
-                       
-                       window.console.log("");
-               },
-               
-               /**
-                * Disables/re-enables the editor autosave feature.
-                * 
-                * @param       {boolean}       forceDisable
-                */
-               toggleEditorAutosave: function(forceDisable) {
-                       _settings.editorAutosave = (forceDisable === true) ? false : !_settings.editorAutosave;
-                       _updateConfig();
-                       
-                       window.console.log("%c\tEditor autosave " + (_settings.editorAutosave ? "enabled" : "disabled"), "font-style: italic");
-               },
-               
-               /**
-                * Enables/disables logging for fired event listener events.
-                * 
-                * @param       {boolean}       forceEnable
-                */
-               toggleEventLogging: function(forceEnable) {
-                       _settings.eventLogging = (forceEnable === true) ? true : !_settings.eventLogging;
-                       _updateConfig();
-                       
-                       window.console.log("%c\tEvent logging " + (_settings.eventLogging ? "enabled" : "disabled"), "font-style: italic");
-               },
-               
-               /**
-                * Internal methods not meant to be called directly.
-                */
-               _internal_: {
-                       enable: function () {
-                               window.Devtools = Devtools;
-                               
-                               window.console.log("%cDevtools for WoltLab Suite loaded", "font-weight: bold");
-                               
-                               if (window.sessionStorage) {
-                                       var settings = window.sessionStorage.getItem("__wsc_devtools_config");
-                                       try {
-                                               if (settings !== null) {
-                                                       _settings = JSON.parse(settings);
-                                               }
-                                       }
-                                       catch (e) {}
-                                       
-                                       if (!_settings.editorAutosave) Devtools.toggleEditorAutosave(true);
-                                       if (_settings.eventLogging) Devtools.toggleEventLogging(true);
-                               }
-                               
-                               window.console.log("Settings are saved per browser session, enter `Devtools.help()` to learn more.");
-                               window.console.log("");
-                       },
-                       
-                       editorAutosave: function () {
-                               return _settings.editorAutosave;
-                       },
-                       
-                       eventLog: function(identifier, action) {
-                               if (_settings.eventLogging) {
-                                       window.console.log("[Devtools.EventLogging] Firing event: " + action + " @ " + identifier);
-                               }
-                       }
-               }
-       };
-       
-       return Devtools;
+define(["require", "exports"], function (require, exports) {
+    "use strict";
+    let _settings = {
+        editorAutosave: true,
+        eventLogging: false,
+    };
+    function _updateConfig() {
+        if (window.sessionStorage) {
+            window.sessionStorage.setItem('__wsc_devtools_config', JSON.stringify(_settings));
+        }
+    }
+    const Devtools = {
+        /**
+         * Prints the list of available commands.
+         */
+        help: () => {
+            window.console.log('');
+            window.console.log('%cAvailable commands:', 'text-decoration: underline');
+            const commands = [];
+            for (const cmd in Devtools) {
+                if (cmd !== '_internal_' && Devtools.hasOwnProperty(cmd)) {
+                    commands.push(cmd);
+                }
+            }
+            commands.sort().forEach(function (cmd) {
+                window.console.log('\tDevtools.' + cmd + '()');
+            });
+            window.console.log('');
+        },
+        /**
+         * Disables/re-enables the editor autosave feature.
+         */
+        toggleEditorAutosave: (forceDisable) => {
+            _settings.editorAutosave = forceDisable ? false : !_settings.editorAutosave;
+            _updateConfig();
+            window.console.log('%c\tEditor autosave ' + (_settings.editorAutosave ? 'enabled' : 'disabled'), 'font-style: italic');
+        },
+        /**
+         * Enables/disables logging for fired event listener events.
+         */
+        toggleEventLogging: function (forceEnable) {
+            _settings.eventLogging = forceEnable ? true : !_settings.eventLogging;
+            _updateConfig();
+            window.console.log('%c\tEvent logging ' + (_settings.eventLogging ? 'enabled' : 'disabled'), 'font-style: italic');
+        },
+        /**
+         * Internal methods not meant to be called directly.
+         */
+        _internal_: {
+            enable: () => {
+                window.Devtools = Devtools;
+                window.console.log('%cDevtools for WoltLab Suite loaded', 'font-weight: bold');
+                if (window.sessionStorage) {
+                    const settings = window.sessionStorage.getItem('__wsc_devtools_config');
+                    try {
+                        if (settings !== null) {
+                            _settings = JSON.parse(settings);
+                        }
+                    }
+                    catch (e) {
+                    }
+                    if (!_settings.editorAutosave)
+                        Devtools.toggleEditorAutosave(true);
+                    if (_settings.eventLogging)
+                        Devtools.toggleEventLogging(true);
+                }
+                window.console.log('Settings are saved per browser session, enter `Devtools.help()` to learn more.');
+                window.console.log('');
+            },
+            editorAutosave: () => _settings.editorAutosave,
+            eventLog: (identifier, action) => {
+                if (_settings.eventLogging) {
+                    window.console.log('[Devtools.EventLogging] Firing event: ' + action + ' @ ' + identifier);
+                }
+            },
+        },
+    };
+    return Devtools;
 });
index 798fd1fdb32bd3eb2a511ed1e20ef8fa08a5fc34..1f8c087eb30843323cd48c216ea0ff9fbc7416a5 100644 (file)
@@ -51,6 +51,9 @@ define(["require", "exports"], function (require, exports) {
          * value as first parameter and the key name second.
          */
         forEach(callback) {
+            if (typeof callback !== 'function') {
+                throw new TypeError('forEach() expects a callback as first parameter.');
+            }
             this._dictionary.forEach(callback);
         }
         /**
index 184b46d55923a5d7f8c0ac1df85da6fc6cd6768b..47556c8bb36d39d456704a744b8073aea2a1c0a1 100644 (file)
 /**
  * Provides basic details on the JavaScript environment.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     Environment (alias)
- * @module     WoltLabSuite/Core/Environment
+ *
+ * @author  Alexander Ebert
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module  Environment (alias)
+ * @module  WoltLabSuite/Core/Environment
  */
-define([], function() {
-       "use strict";
-       
-       var _browser = 'other';
-       var _editor = 'none';
-       var _platform = 'desktop';
-       var _touch = false;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Environment
-        */
-       return {
-               /**
-                * Determines environment variables.
-                */
-               setup: function() {
-                       if (typeof window.chrome === 'object') {
-                               // this detects Opera as well, we could check for window.opr if we need to
-                               _browser = 'chrome';
-                       }
-                       else {
-                               var styles = window.getComputedStyle(document.documentElement);
-                               for (var i = 0, length = styles.length; i < length; i++) {
-                                       var property = styles[i];
-                                       
-                                       if (property.indexOf('-ms-') === 0) {
-                                               // it is tempting to use 'msie', but it wouldn't really represent 'Edge'
-                                               _browser = 'microsoft';
-                                       }
-                                       else if (property.indexOf('-moz-') === 0) {
-                                               _browser = 'firefox';
-                                       }
-                                       else if (_browser !== 'firefox' && property.indexOf('-webkit-') === 0) {
-                                               _browser = 'safari';
-                                       }
-                               }
-                       }
-                       
-                       var ua = window.navigator.userAgent.toLowerCase();
-                       if (ua.indexOf('crios') !== -1) {
-                               _browser = 'chrome';
-                               _platform = 'ios';
-                       }
-                       else if (/(?:iphone|ipad|ipod)/.test(ua)) {
-                               _browser = 'safari';
-                               _platform = 'ios';
-                       }
-                       else if (ua.indexOf('android') !== -1) {
-                               _platform = 'android';
-                       }
-                       else if (ua.indexOf('iemobile') !== -1) {
-                               _browser = 'microsoft';
-                               _platform = 'windows';
-                       }
-                       
-                       if (_platform === 'desktop' && (ua.indexOf('mobile') !== -1 || ua.indexOf('tablet') !== -1)) {
-                               _platform = 'mobile';
-                       }
-                       
-                       _editor = 'redactor';
-                       _touch = (!!('ontouchstart' in window) || (!!('msMaxTouchPoints' in window.navigator) && window.navigator.msMaxTouchPoints > 0) || window.DocumentTouch && document instanceof DocumentTouch);
-                       
-                       // The iPad Pro 12.9" masquerades as a desktop browser.
-                       if (window.navigator.platform === 'MacIntel' && window.navigator.maxTouchPoints > 1) {
-                               _browser = 'safari';
-                               _platform = 'ios';
-                       }
-               },
-               
-               /**
-                * Returns the lower-case browser identifier.
-                * 
-                * Possible values:
-                *  - chrome: Chrome and Opera
-                *  - firefox
-                *  - microsoft: Internet Explorer and Microsoft Edge
-                *  - safari
-                * 
-                * @return      {string}        browser identifier
-                */
-               browser: function() {
-                       return _browser;
-               },
-               
-               /**
-                * Returns the available editor's name or an empty string.
-                * 
-                * @return      {string}        editor name
-                */
-               editor: function() {
-                       return _editor;
-               },
-               
-               /**
-                * Returns the browser platform.
-                * 
-                * Possible values:
-                *  - desktop
-                *  - android
-                *  - ios: iPhone, iPad and iPod
-                *  - windows: Windows on phones/tablets
-                * 
-                * @return      {string}        browser platform
-                */
-               platform: function() {
-                       return _platform;
-               },
-               
-               /**
-                * Returns true if browser is potentially used with a touchscreen.
-                * 
-                * Warning: Detecting touch is unreliable and should be avoided at all cost.
-                * 
-                * @deprecated  3.0 - exists for backward-compatibility only, will be removed in the future
-                * 
-                * @return      {boolean}       true if a touchscreen is present
-                */
-               touch: function() {
-                       return _touch;
-               }
-       };
+define(["require", "exports"], function (require, exports) {
+    "use strict";
+    Object.defineProperty(exports, "__esModule", { value: true });
+    exports.touch = exports.platform = exports.editor = exports.browser = exports.setup = void 0;
+    let _browser = 'other';
+    let _editor = 'none';
+    let _platform = 'desktop';
+    let _touch = false;
+    /**
+     * Determines environment variables.
+     */
+    function setup() {
+        if (typeof window.chrome === 'object') {
+            // this detects Opera as well, we could check for window.opr if we need to
+            _browser = 'chrome';
+        }
+        else {
+            const styles = window.getComputedStyle(document.documentElement);
+            for (let i = 0, length = styles.length; i < length; i++) {
+                const property = styles[i];
+                if (property.indexOf('-ms-') === 0) {
+                    // it is tempting to use 'msie', but it wouldn't really represent 'Edge'
+                    _browser = 'microsoft';
+                }
+                else if (property.indexOf('-moz-') === 0) {
+                    _browser = 'firefox';
+                }
+                else if (_browser !== 'firefox' && property.indexOf('-webkit-') === 0) {
+                    _browser = 'safari';
+                }
+            }
+        }
+        const ua = window.navigator.userAgent.toLowerCase();
+        if (ua.indexOf('crios') !== -1) {
+            _browser = 'chrome';
+            _platform = 'ios';
+        }
+        else if (/(?:iphone|ipad|ipod)/.test(ua)) {
+            _browser = 'safari';
+            _platform = 'ios';
+        }
+        else if (ua.indexOf('android') !== -1) {
+            _platform = 'android';
+        }
+        else if (ua.indexOf('iemobile') !== -1) {
+            _browser = 'microsoft';
+            _platform = 'windows';
+        }
+        if (_platform === 'desktop' && (ua.indexOf('mobile') !== -1 || ua.indexOf('tablet') !== -1)) {
+            _platform = 'mobile';
+        }
+        _editor = 'redactor';
+        _touch = (('ontouchstart' in window) || (('msMaxTouchPoints' in window.navigator) && window.navigator.msMaxTouchPoints > 0) || window.DocumentTouch && document instanceof window.DocumentTouch);
+        // The iPad Pro 12.9" masquerades as a desktop browser.
+        if (window.navigator.platform === 'MacIntel' && window.navigator.maxTouchPoints > 1) {
+            _browser = 'safari';
+            _platform = 'ios';
+        }
+    }
+    exports.setup = setup;
+    /**
+     * Returns the lower-case browser identifier.
+     *
+     * Possible values:
+     *  - chrome: Chrome and Opera
+     *  - firefox
+     *  - microsoft: Internet Explorer and Microsoft Edge
+     *  - safari
+     */
+    function browser() {
+        return _browser;
+    }
+    exports.browser = browser;
+    /**
+     * Returns the available editor's name or an empty string.
+     */
+    function editor() {
+        return _editor;
+    }
+    exports.editor = editor;
+    /**
+     * Returns the browser platform.
+     *
+     * Possible values:
+     *  - desktop
+     *  - android
+     *  - ios: iPhone, iPad and iPod
+     *  - windows: Windows on phones/tablets
+     */
+    function platform() {
+        return _platform;
+    }
+    exports.platform = platform;
+    /**
+     * Returns true if browser is potentially used with a touchscreen.
+     *
+     * Warning: Detecting touch is unreliable and should be avoided at all cost.
+     *
+     * @deprecated  3.0 - exists for backward-compatibility only, will be removed in the future
+     */
+    function touch() {
+        return _touch;
+    }
+    exports.touch = touch;
 });
index a7390b3cedfe9a3d7417920ff10d6d275f4bff7f..1e6a24cd8d8318410665aa410072c21e99c29c4b 100644 (file)
 /**
  * Provides helper functions for file handling.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/FileUtil
+ *
+ * @author  Matthias Schmidt
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module  WoltLabSuite/Core/FileUtil
  */
-define(['Dictionary', 'StringUtil'], function(Dictionary, StringUtil) {
-       "use strict";
-       
-       var _fileExtensionIconMapping = Dictionary.fromObject({
-               // archive
-               zip: 'archive',
-               rar: 'archive',
-               tar: 'archive',
-               gz: 'archive',
-               
-               // audio
-               mp3: 'audio',
-               ogg: 'audio',
-               wav: 'audio',
-               
-               // code
-               php: 'code',
-               html: 'code',
-               htm: 'code',
-               tpl: 'code',
-               js: 'code',
-               
-               // excel
-               xls: 'excel',
-               ods: 'excel',
-               xlsx: 'excel',
-               
-               // image
-               gif: 'image',
-               jpg: 'image',
-               jpeg: 'image',
-               png: 'image',
-               bmp: 'image',
-               webp: 'image',
-               
-               // video
-               avi: 'video',
-               wmv: 'video',
-               mov: 'video',
-               mp4: 'video',
-               mpg: 'video',
-               mpeg: 'video',
-               flv: 'video',
-               
-               // pdf
-               pdf: 'pdf',
-               
-               // powerpoint
-               ppt: 'powerpoint',
-               pptx: 'powerpoint',
-               
-               // text
-               txt: 'text',
-               
-               // word
-               doc: 'word',
-               docx: 'word',
-               odt: 'word'
-       });
-       
-       var _mimeTypeExtensionMapping = Dictionary.fromObject({
-               // archive
-               'application/zip': 'zip',
-               'application/x-zip-compressed': 'zip',
-               'application/rar': 'rar',
-               'application/vnd.rar': 'rar',
-               'application/x-rar-compressed': 'rar',
-               'application/x-tar': 'tar',
-               'application/x-gzip': 'gz',
-               'application/gzip': 'gz',
-
-               // audio
-               'audio/mpeg': 'mp3',
-               'audio/mp3': 'mp3',
-               'audio/ogg': 'ogg',
-               'audio/x-wav': 'wav',
-
-               // code
-               'application/x-php': 'php',
-               'text/html': 'html',
-               'application/javascript': 'js',
-
-               // excel
-               'application/vnd.ms-excel': 'xls',
-               'application/vnd.oasis.opendocument.spreadsheet': 'ods',
-               'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'xlsx',
-
-               // image
-               'image/gif': 'gif',
-               'image/jpeg': 'jpg',
-               'image/png': 'png',
-               'image/x-ms-bmp': 'bmp',
-               'image/bmp': 'bmp',
-               'image/webp': 'webp',
-
-               // video
-               'video/x-msvideo': 'avi',
-               'video/x-ms-wmv': 'wmv',
-               'video/quicktime': 'mov',
-               'video/mp4': 'mp4',
-               'video/mpeg': 'mpg',
-               'video/x-flv': 'flv',
-
-               // pdf
-               'application/pdf': 'pdf',
-
-               // powerpoint
-               'application/vnd.ms-powerpoint': 'ppt',
-               'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'pptx',
-
-               // text
-               'text/plain': 'txt',
-
-               // word
-               'application/msword': 'doc',
-               'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'docx',
-               'application/vnd.oasis.opendocument.text': 'odt'
-       });
-       
-       return {
-               /**
-                * Formats the given filesize.
-                * 
-                * @param       {integer}       byte            number of bytes
-                * @param       {integer}       precision       number of decimals
-                * @return      {string}        formatted filesize
-                */
-               formatFilesize: function(byte, precision) {
-                       if (precision === undefined) {
-                               precision = 2;
-                       }
-                       
-                       var symbol = 'Byte';
-                       if (byte >= 1000) {
-                               byte /= 1000;
-                               symbol = 'kB';
-                       }
-                       if (byte >= 1000) {
-                               byte /= 1000;
-                               symbol = 'MB';
-                       }
-                       if (byte >= 1000) {
-                               byte /= 1000;
-                               symbol = 'GB';
-                       }
-                       if (byte >= 1000) {
-                               byte /= 1000;
-                               symbol = 'TB';
-                       }
-                       
-                       return StringUtil.formatNumeric(byte, -precision) + ' ' + symbol;
-               },
-               
-               /**
-                * Returns the icon name for given filename.
-                * 
-                * Note: For any file icon name like `fa-file-word`, only `word`
-                * will be returned by this method.
-                *
-                * @parsm       {string}        filename        name of file for which icon name will be returned
-                * @return      {string}        FontAwesome icon name
-                */
-               getIconNameByFilename: function(filename) {
-                       var lastDotPosition = filename.lastIndexOf('.');
-                       if (lastDotPosition !== false) {
-                               var extension = filename.substr(lastDotPosition + 1);
-                               
-                               if (_fileExtensionIconMapping.has(extension)) {
-                                       return _fileExtensionIconMapping.get(extension);
-                               }
-                       }
-                       
-                       return '';
-               },
-               
-               /**
-                * Returns a known file extension including a leading dot or an empty string.
-                *
-                * @param       mimetype        the mimetype to get the common file extension for
-                * @returns     {string}        the file dot prefixed extension or an empty string
-                */
-               getExtensionByMimeType: function (mimetype) {
-                       if (_mimeTypeExtensionMapping.has(mimetype)) {
-                               return '.' + _mimeTypeExtensionMapping.get(mimetype);
-                       }
-                       
-                       return '';
-               },
-               
-               /**
-                * Constructs a File object from a Blob
-                *
-                * @param       blob            the blob to convert
-                * @param       filename        the filename
-                * @returns     {File}          the File object
-                */
-               blobToFile: function (blob, filename) {
-                       var ext = this.getExtensionByMimeType(blob.type);
-                       var File = window.File;
-                       
-                       try {
-                               // IE11 does not support the file constructor
-                               new File([], 'ie11-check');
-                       }
-                       catch (error) {
-                               // Create a good enough File object based on the Blob prototype
-                               File = function File(chunks, filename, options) {
-                                       var self = Blob.call(this, chunks, options);
-                                       
-                                       self.name = filename;
-                                       self.lastModifiedDate = new Date();
-                                       
-                                       return self;
-                               };
-                               
-                               File.prototype = Object.create(window.File.prototype);
-                       }
-                       
-                       return new File([blob], filename + ext, {type: blob.type});
-               },
-       };
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    o[k2] = m[k];
+}));
+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
+    Object.defineProperty(o, "default", { enumerable: true, value: v });
+}) : function(o, v) {
+    o["default"] = v;
+});
+var __importStar = (this && this.__importStar) || function (mod) {
+    if (mod && mod.__esModule) return mod;
+    var result = {};
+    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+    __setModuleDefault(result, mod);
+    return result;
+};
+var __importDefault = (this && this.__importDefault) || function (mod) {
+    return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+define(["require", "exports", "./Dictionary", "./StringUtil"], function (require, exports, Dictionary_1, StringUtil) {
+    "use strict";
+    Object.defineProperty(exports, "__esModule", { value: true });
+    exports.blobToFile = exports.getExtensionByMimeType = exports.getIconNameByFilename = exports.formatFilesize = void 0;
+    Dictionary_1 = __importDefault(Dictionary_1);
+    StringUtil = __importStar(StringUtil);
+    const _fileExtensionIconMapping = Dictionary_1.default.fromObject({
+        // archive
+        zip: 'archive',
+        rar: 'archive',
+        tar: 'archive',
+        gz: 'archive',
+        // audio
+        mp3: 'audio',
+        ogg: 'audio',
+        wav: 'audio',
+        // code
+        php: 'code',
+        html: 'code',
+        htm: 'code',
+        tpl: 'code',
+        js: 'code',
+        // excel
+        xls: 'excel',
+        ods: 'excel',
+        xlsx: 'excel',
+        // image
+        gif: 'image',
+        jpg: 'image',
+        jpeg: 'image',
+        png: 'image',
+        bmp: 'image',
+        webp: 'image',
+        // video
+        avi: 'video',
+        wmv: 'video',
+        mov: 'video',
+        mp4: 'video',
+        mpg: 'video',
+        mpeg: 'video',
+        flv: 'video',
+        // pdf
+        pdf: 'pdf',
+        // powerpoint
+        ppt: 'powerpoint',
+        pptx: 'powerpoint',
+        // text
+        txt: 'text',
+        // word
+        doc: 'word',
+        docx: 'word',
+        odt: 'word',
+    });
+    const _mimeTypeExtensionMapping = Dictionary_1.default.fromObject({
+        // archive
+        'application/zip': 'zip',
+        'application/x-zip-compressed': 'zip',
+        'application/rar': 'rar',
+        'application/vnd.rar': 'rar',
+        'application/x-rar-compressed': 'rar',
+        'application/x-tar': 'tar',
+        'application/x-gzip': 'gz',
+        'application/gzip': 'gz',
+        // audio
+        'audio/mpeg': 'mp3',
+        'audio/mp3': 'mp3',
+        'audio/ogg': 'ogg',
+        'audio/x-wav': 'wav',
+        // code
+        'application/x-php': 'php',
+        'text/html': 'html',
+        'application/javascript': 'js',
+        // excel
+        'application/vnd.ms-excel': 'xls',
+        'application/vnd.oasis.opendocument.spreadsheet': 'ods',
+        'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'xlsx',
+        // image
+        'image/gif': 'gif',
+        'image/jpeg': 'jpg',
+        'image/png': 'png',
+        'image/x-ms-bmp': 'bmp',
+        'image/bmp': 'bmp',
+        'image/webp': 'webp',
+        // video
+        'video/x-msvideo': 'avi',
+        'video/x-ms-wmv': 'wmv',
+        'video/quicktime': 'mov',
+        'video/mp4': 'mp4',
+        'video/mpeg': 'mpg',
+        'video/x-flv': 'flv',
+        // pdf
+        'application/pdf': 'pdf',
+        // powerpoint
+        'application/vnd.ms-powerpoint': 'ppt',
+        'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'pptx',
+        // text
+        'text/plain': 'txt',
+        // word
+        'application/msword': 'doc',
+        'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'docx',
+        'application/vnd.oasis.opendocument.text': 'odt',
+    });
+    /**
+     * Formats the given filesize.
+     */
+    function formatFilesize(byte, precision) {
+        if (precision === undefined) {
+            precision = 2;
+        }
+        let symbol = 'Byte';
+        if (byte >= 1000) {
+            byte /= 1000;
+            symbol = 'kB';
+        }
+        if (byte >= 1000) {
+            byte /= 1000;
+            symbol = 'MB';
+        }
+        if (byte >= 1000) {
+            byte /= 1000;
+            symbol = 'GB';
+        }
+        if (byte >= 1000) {
+            byte /= 1000;
+            symbol = 'TB';
+        }
+        return StringUtil.formatNumeric(byte, -precision) + ' ' + symbol;
+    }
+    exports.formatFilesize = formatFilesize;
+    /**
+     * Returns the icon name for given filename.
+     *
+     * Note: For any file icon name like `fa-file-word`, only `word`
+     * will be returned by this method.
+     */
+    function getIconNameByFilename(filename) {
+        const lastDotPosition = filename.lastIndexOf('.');
+        if (lastDotPosition !== -1) {
+            const extension = filename.substr(lastDotPosition + 1);
+            if (_fileExtensionIconMapping.has(extension)) {
+                return _fileExtensionIconMapping.get(extension);
+            }
+        }
+        return '';
+    }
+    exports.getIconNameByFilename = getIconNameByFilename;
+    /**
+     * Returns a known file extension including a leading dot or an empty string.
+     */
+    function getExtensionByMimeType(mimetype) {
+        if (_mimeTypeExtensionMapping.has(mimetype)) {
+            return '.' + _mimeTypeExtensionMapping.get(mimetype);
+        }
+        return '';
+    }
+    exports.getExtensionByMimeType = getExtensionByMimeType;
+    /**
+     * Constructs a File object from a Blob
+     *
+     * @param       blob            the blob to convert
+     * @param       filename        the filename
+     * @returns     {File}          the File object
+     */
+    function blobToFile(blob, filename) {
+        const ext = this.getExtensionByMimeType(blob.type);
+        return new File([blob], filename + ext, { type: blob.type });
+    }
+    exports.blobToFile = blobToFile;
 });
index adf440f2498fe79fae42f0368c5053e735a44f7c..7dd612918d20386cf35438cdf353249b513df344 100644 (file)
 /**
  * Generates plural phrases for the `plural` template plugin.
- * 
- * @author     Matthias Schmidt, Marcel Werk
- * @copyright  2001-2020 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/I18n/Plural
+ *
+ * @author  Matthias Schmidt, Marcel Werk
+ * @copyright  2001-2020 WoltLab GmbH
+ * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module  WoltLabSuite/Core/I18n/Plural
  */
-define(['StringUtil'], function(StringUtil) {
-       "use strict";
-       
-       var PLURAL_FEW = 'few';
-       var PLURAL_MANY = 'many';
-       var PLURAL_ONE = 'one';
-       var PLURAL_OTHER = 'other';
-       var PLURAL_TWO = 'two';
-       var PLURAL_ZERO = 'zero';
-       
-       return {
-               /**
-                * Returns the plural category for the given value.
-                *
-                * @param       {number}        value
-                * @param       {?string}       languageCode
-                * @return      string
-                */
-               getCategory: function(value, languageCode) {
-                       if (!languageCode) {
-                               languageCode = document.documentElement.lang;
-                       }
-                       
-                       // Fallback: handle unknown languages as English
-                       if (typeof this[languageCode] !== 'function') {
-                               languageCode = 'en';
-                       }
-                       
-                       var category = this[languageCode](value);
-                       if (category) {
-                               return category;
-                       }
-                       
-                       return PLURAL_OTHER;
-               },
-               
-               /**
-                * Returns the value for a `plural` element used in the template.
-                * 
-                * @param       {object}        parameters
-                * @see         wcf\system\template\plugin\PluralFunctionTemplatePlugin::execute()
-                */
-               getCategoryFromTemplateParameters: function(parameters) {
-                       if (!parameters['value'] ) {
-                               throw new Error('Missing parameter value');
-                       }
-                       if (!parameters['other']) {
-                               throw new Error('Missing parameter other');
-                       }
-                       
-                       var value = parameters['value'];
-                       if (Array.isArray(value)) {
-                               value = value.length;
-                       }
-                       
-                       // handle numeric attributes
-                       for (var key in parameters) {
-                               if (objOwns(parameters, key) && key == ~~key && key == value) {
-                                       return parameters[key];
-                               }
-                       }
-                       
-                       var category = this.getCategory(value);
-                       if (!parameters[category]) {
-                               category = PLURAL_OTHER;
-                       }
-                       
-                       var string = parameters[category];
-                       if (string.indexOf('#') !== -1) {
-                               return string.replace('#', StringUtil.formatNumeric(value));
-                       }
-                       
-                       return string;
-               },
-               
-               /**
-                * `f` is the fractional number as a whole number (1.234 yields 234)
-                * 
-                * @param       {number}        n
-                * @return      {integer}
-                */
-               getF: function(n) {
-                       n = n.toString();
-                       var pos = n.indexOf('.');
-                       if (pos === -1) {
-                               return 0;
-                       }
-                       
-                       return parseInt(n.substr(pos + 1), 10);
-               },
-               
-               /**
-                * `v` represents the number of digits of the fractional part (1.234 yields 3)
-                * 
-                * @param       {number}        n
-                * @return      {integer}
-                */
-               getV: function(n) {
-                       return n.toString().replace(/^[^.]*\.?/, '').length;
-               },
-               
-               // Afrikaans
-               af: function(n) {
-                       if (n == 1) return PLURAL_ONE;
-               },
-               
-               // Amharic
-               am: function(n) {
-                       var i = Math.floor(Math.abs(n));
-                       if (n == 1 || i === 0) return PLURAL_ONE;
-               },
-               
-               // Arabic
-               ar: function(n) {
-                       if (n == 0) return PLURAL_ZERO;
-                       if (n == 1) return PLURAL_ONE;
-                       if (n == 2) return PLURAL_TWO;
-                       
-                       var mod100 = n % 100;
-                       if (mod100 >= 3 && mod100 <= 10) return PLURAL_FEW;
-                       if (mod100 >= 11 && mod100 <= 99) return PLURAL_MANY;
-               },
-               
-               // Assamese
-               as: function(n) {
-                       var i = Math.floor(Math.abs(n));
-                       if (n == 1 || i === 0) return PLURAL_ONE;
-               },
-               
-               // Azerbaijani
-               az: function(n) {
-                       if (n == 1) return PLURAL_ONE;
-               },
-               
-               // Belarusian
-               be: function(n) {
-                       var mod10 = n % 10;
-                       var mod100 = n % 100;
-                       
-                       if (mod10 == 1 && mod100 != 11) return PLURAL_ONE;
-                       if (mod10 >= 2 && mod10 <= 4 && !(mod100 >= 12 && mod100 <= 14)) return PLURAL_FEW;
-                       if (mod10 == 0 || (mod10 >= 5 && mod10 <= 9) || (mod100 >= 11 && mod100 <= 14)) return PLURAL_MANY;
-               },
-               
-               // Bulgarian
-               bg: function(n) {
-                       if (n == 1) return PLURAL_ONE;
-               },
-               
-               // Bengali
-               bn: function(n) {
-                       var i = Math.floor(Math.abs(n));
-                       if (n == 1 || i === 0) return PLURAL_ONE;
-               },
-               
-               // Tibetan
-               bo: function(n) {},
-               
-               // Bosnian
-               bs: function(n) {
-                       var v = this.getV(n);
-                       var f = this.getF(n);
-                       var mod10 = n % 10;
-                       var mod100 = n % 100;
-                       var fMod10 = f % 10;
-                       var fMod100 = f % 100;
-                       
-                       if ((v == 0 && mod10 == 1 && mod100 != 11) || (fMod10 == 1 && fMod100 != 11)) return PLURAL_ONE;
-                       if ((v == 0 && mod10 >= 2 && mod10 <= 4 && mod100 >= 12 && mod100 <= 14)
-                               || (fMod10 >= 2 && fMod10 <= 4 && fMod100 >= 12 && fMod100 <= 14)) return PLURAL_FEW;
-               },
-               
-               // Czech
-               cs: function(n) {
-                       var v = this.getV(n);
-                       
-                       if (n == 1 && v === 0) return PLURAL_ONE;
-                       if (n >= 2 && n <= 4 && v === 0) return PLURAL_FEW;
-                       if (v === 0) return PLURAL_MANY;
-               },
-               
-               // Welsh
-               cy: function(n) {
-                       if (n == 0) return PLURAL_ZERO;
-                       if (n == 1) return PLURAL_ONE;
-                       if (n == 2) return PLURAL_TWO;
-                       if (n == 3) return PLURAL_FEW;
-                       if (n == 6) return PLURAL_MANY;
-               },
-               
-               // Danish
-               da: function(n) {
-                       if (n > 0 && n < 2) return PLURAL_ONE;
-               },
-               
-               // Greek
-               el: function(n) {
-                       if (n == 1) return PLURAL_ONE;
-               },
-               
-               // Catalan (ca)
-               // German (de)
-               // English (en)
-               // Estonian (et)
-               // Finnish (fi)
-               // Italian (it)
-               // Dutch (nl)
-               // Swedish (sv)
-               // Swahili (sw)
-               // Urdu (ur)
-               en: function(n) {
-                       if (n == 1 && this.getV(n) === 0) return PLURAL_ONE;
-               },
-               
-               // Spanish
-               es: function(n) {
-                       if (n == 1) return PLURAL_ONE;
-               },
-               
-               // Basque
-               eu: function(n) {
-                       if (n == 1) return PLURAL_ONE;
-               },
-               
-               // Persian
-               fa: function(n) {
-                       if (n >= 0 && n <= 1) return PLURAL_ONE;
-               },
-               
-               // French
-               fr: function(n) {
-                       if (n >= 0 && n < 2) return PLURAL_ONE;
-               },
-               
-               // Irish
-               ga: function(n) {
-                       if (n == 1) return PLURAL_ONE;
-                       if (n == 2) return PLURAL_TWO;
-                       if (n == 3 || n == 4 || n == 5 || n == 6) return PLURAL_FEW;
-                       if (n == 7 || n == 8 || n == 9 || n == 10) return PLURAL_MANY;
-               },
-               
-               // Gujarati
-               gu: function(n) {
-                       if (n >= 0 && n <= 1) return PLURAL_ONE;
-               },
-               
-               // Hebrew
-               he: function(n) {
-                       var v = this.getV(n);
-       
-                       if (n == 1 && v === 0) return PLURAL_ONE;
-                       if (n == 2 && v === 0) return PLURAL_TWO;
-                       if (n > 10 && v === 0 && n % 10 == 0) return PLURAL_MANY;
-               },
-               
-               // Hindi
-               hi: function(n) {
-                       if (n >= 0 && n <= 1) return PLURAL_ONE;
-               },
-               
-               // Croatian
-               hr: function(n) {
-                       // same as Bosnian
-                       return this.bs(n);
-               },
-               
-               // Hungarian
-               hu: function(n) {
-                       if (n == 1) return PLURAL_ONE;
-               },
-               
-               // Armenian
-               hy: function(n) {
-                       if (n >= 0 && n < 2) return PLURAL_ONE;
-               },
-               
-               // Indonesian
-               id: function(n) {},
-               
-               // Icelandic
-               is: function(n) {
-                       var f = this.getF(n);
-                       
-                       if (f === 0 && n % 10 === 1 && !(n % 100 === 11) || !(f === 0)) return PLURAL_ONE;
-               },
-               
-               // Japanese
-               ja: function(n) {},
-               
-               // Javanese
-               jv: function(n) {},
-               
-               // Georgian
-               ka: function(n) {
-                       if (n == 1) return PLURAL_ONE;
-               },
-               
-               // Kazakh
-               kk: function(n) {
-                       if (n == 1) return PLURAL_ONE;
-               },
-               
-               // Khmer
-               km: function(n) {},
-               
-               // Kannada
-               kn: function(n) {
-                       if (n >= 0 && n <= 1) return PLURAL_ONE;
-               },
-               
-               // Korean
-               ko: function(n) {},
-               
-               // Kurdish
-               ku: function(n) {
-                       if (n == 1) return PLURAL_ONE;
-               },
-               
-               // Kyrgyz
-               ky: function(n) {
-                       if (n == 1) return PLURAL_ONE;
-               },
-               
-               // Luxembourgish
-               lb: function(n) {
-                       if (n == 1) return PLURAL_ONE;
-               },
-               
-               // Lao
-               lo: function(n) {},
-               
-               // Lithuanian
-               lt: function(n) {
-                       var mod10 = n % 10;
-                       var mod100 = n % 100;
-                       
-                       if (mod10 == 1 && !(mod100 >= 11 && mod100 <= 19)) return PLURAL_ONE;
-                       if (mod10 >= 2 && mod10 <= 9 && !(mod100 >= 11 && mod100 <= 19)) return PLURAL_FEW;
-                       if (this.getF(n) != 0) return PLURAL_MANY;
-               },
-               
-               // Latvian
-               lv: function(n) {
-                       var mod10 = n % 10;
-                       var mod100 = n % 100;
-                       var v = this.getV(n);
-                       var f = this.getF(n);
-                       var fMod10 = f % 10;
-                       var fMod100 = f % 100;
-                       
-                       if (mod10 == 0 || (mod100 >= 11 && mod100 <= 19) || (v == 2 && fMod100 >= 11 && fMod100 <= 19)) return PLURAL_ZERO;
-                       if ((mod10 == 1 && mod100 != 11) || (v == 2 && fMod10 == 1 && fMod100 != 11) || (v != 2 && fMod10 == 1)) return PLURAL_ONE;
-               },
-               
-               // Macedonian
-               mk: function(n) {
-                       var v = this.getV(n);
-                       var f = this.getF(n);
-                       var mod10 = n % 10;
-                       var mod100 = n % 100;
-                       var fMod10 = f % 10;
-                       var fMod100 = f % 100;
-                       
-                       if ((v == 0 && mod10 == 1 && mod100 != 11) || (fMod10 == 1 && fMod100 != 11)) return PLURAL_ONE;
-               },
-               
-               // Malayalam
-               ml: function(n) {
-                       if (n == 1) return PLURAL_ONE;
-               },
-               
-               // Mongolian 
-               mn: function(n) {
-                       if (n == 1) return PLURAL_ONE;
-               },
-               
-               // Marathi 
-               mr: function(n) {
-                       if (n == 1) return PLURAL_ONE;
-               },
-               
-               // Malay 
-               ms: function(n) {},
-               
-               // Maltese 
-               mt: function(n) {
-                       var mod100 = n % 100;
-                       
-                       if (n == 1) return PLURAL_ONE;
-                       if (n == 0 || (mod100 >= 2 && mod100 <= 10)) return PLURAL_FEW;
-                       if (mod100 >= 11 && mod100 <= 19) return PLURAL_MANY;
-               },
-               
-               // Burmese
-               my: function(n) {},
-               
-               // Norwegian
-               no: function(n) {
-                       if (n == 1) return PLURAL_ONE;
-               },
-               
-               // Nepali
-               ne: function(n) {
-                       if (n == 1) return PLURAL_ONE;
-               },
-               
-               // Odia
-               or: function(n) {
-                       if (n == 1) return PLURAL_ONE;
-               },
-               
-               // Punjabi
-               pa: function(n) {
-                       if (n == 1 || n == 0) return PLURAL_ONE;
-               },
-               
-               // Polish
-               pl: function(n) {
-                       var v = this.getV(n);
-                       var mod10 = n % 10;
-                       var mod100 = n % 100;
-       
-                       if (n == 1 && v == 0) return PLURAL_ONE;
-                       if (v == 0 && mod10 >= 2 && mod10 <= 4 && !(mod100 >= 12 && mod100 <= 14)) return PLURAL_FEW;
-                       if (v == 0 && ((n != 1 && mod10 >= 0 && mod10 <= 1) || (mod10 >= 5 && mod10 <= 9) || (mod100 >= 12 && mod100 <= 14))) return PLURAL_MANY;
-               },
-               
-               // Pashto
-               ps: function(n) {
-                       if (n == 1) return PLURAL_ONE;
-               },
-               
-               // Portuguese
-               pt: function(n) {
-                       if (n >= 0 && n < 2) return PLURAL_ONE;
-               },
-               
-               // Romanian
-               ro: function(n) {
-                       var v = this.getV(n);
-                       var mod100 = n % 100;
-                       
-                       if (n == 1 && v === 0) return PLURAL_ONE;
-                       if (v != 0 || n == 0 || (mod100 >= 2 && mod100 <= 19)) return PLURAL_FEW;
-               },
-               
-               // Russian
-               ru: function(n) {
-                       var mod10 = n % 10;
-                       var mod100 = n % 100;
-                       
-                       if (this.getV(n) == 0) {
-                               if (mod10 == 1 && mod100 != 11) return PLURAL_ONE;
-                               if (mod10 >= 2 && mod10 <= 4 && !(mod100 >= 12 && mod100 <= 14)) return PLURAL_FEW;
-                               if (mod10 == 0 || (mod10 >= 5 && mod10 <= 9) || (mod100 >= 11 && mod100 <= 14)) return PLURAL_MANY;
-                       }
-               },
-               
-               // Sindhi
-               sd: function(n) {
-                       if (n == 1) return PLURAL_ONE;
-               },
-               
-               // Sinhala
-               si: function(n) {
-                       if (n == 0 || n == 1 || (Math.floor(n) == 0 && this.getF(n) == 1)) return PLURAL_ONE;
-               },
-               
-               // Slovak
-               sk: function(n) {
-                       // same as Czech
-                       return this.cs(n);
-               },
-               
-               // Slovenian
-               sl: function(n) {
-                       var v = this.getV(n);
-                       var mod100 = n % 100;
-                       
-                       if (v == 0 && mod100 == 1) return PLURAL_ONE;
-                       if (v == 0 && mod100 == 2) return PLURAL_TWO;
-                       if ((v == 0 && (mod100 == 3 || mod100 == 4)) || v != 0) return PLURAL_FEW;
-               },
-               
-               // Albanian
-               sq: function(n) {
-                       if (n == 1) return PLURAL_ONE;
-               },
-               
-               // Serbian
-               sr: function(n) {
-                       // same as Bosnian
-                       return this.bs(n);
-               },
-               
-               // Tamil
-               ta: function(n) {
-                       if (n == 1) return PLURAL_ONE;
-               },
-               
-               // Telugu
-               te: function(n) {
-                       if (n == 1) return PLURAL_ONE;
-               },
-               
-               // Tajik
-               tg: function(n) {},
-               
-               // Thai
-               th: function(n) {},
-               
-               // Turkmen
-               tk: function(n) {
-                       if (n == 1) return PLURAL_ONE;
-               },
-               
-               // Turkish
-               tr: function(n) {
-                       if (n == 1) return PLURAL_ONE;
-               },
-               
-               // Uyghur
-               ug: function(n) {
-                       if (n == 1) return PLURAL_ONE;
-               },
-               
-               // Ukrainian
-               uk: function(n) {
-                       // same as Russian
-                       return this.ru(n);
-               },
-               
-               // Uzbek
-               uz: function(n) {
-                       if (n == 1) return PLURAL_ONE;
-               },
-               
-               // Vietnamese
-               vi: function(n) {},
-               
-               // Chinese
-               zh: function(n) {}
-       };
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    o[k2] = m[k];
+}));
+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
+    Object.defineProperty(o, "default", { enumerable: true, value: v });
+}) : function(o, v) {
+    o["default"] = v;
+});
+var __importStar = (this && this.__importStar) || function (mod) {
+    if (mod && mod.__esModule) return mod;
+    var result = {};
+    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+    __setModuleDefault(result, mod);
+    return result;
+};
+define(["require", "exports", "../StringUtil"], function (require, exports, StringUtil) {
+    "use strict";
+    StringUtil = __importStar(StringUtil);
+    const PLURAL_FEW = 'few';
+    const PLURAL_MANY = 'many';
+    const PLURAL_ONE = 'one';
+    const PLURAL_OTHER = 'other';
+    const PLURAL_TWO = 'two';
+    const PLURAL_ZERO = 'zero';
+    const Plural = {
+        /**
+         * Returns the plural category for the given value.
+         */
+        getCategory: function (value, languageCode) {
+            if (!languageCode) {
+                languageCode = document.documentElement.lang;
+            }
+            // Fallback: handle unknown languages as English
+            if (typeof this[languageCode] !== 'function') {
+                languageCode = 'en';
+            }
+            const category = this[languageCode](value);
+            if (category) {
+                return category;
+            }
+            return PLURAL_OTHER;
+        },
+        /**
+         * Returns the value for a `plural` element used in the template.
+         *
+         * @see    wcf\system\template\plugin\PluralFunctionTemplatePlugin::execute()
+         */
+        getCategoryFromTemplateParameters: function (parameters) {
+            if (!parameters['value']) {
+                throw new Error('Missing parameter value');
+            }
+            if (!parameters['other']) {
+                throw new Error('Missing parameter other');
+            }
+            let value = parameters['value'];
+            if (Array.isArray(value)) {
+                value = value.length;
+            }
+            // handle numeric attributes
+            for (const key in parameters) {
+                if (parameters.hasOwnProperty(key) && key.toString() === (~~key).toString() && key == value) {
+                    return parameters[key];
+                }
+            }
+            let category = this.getCategory(value);
+            if (!parameters[category]) {
+                category = PLURAL_OTHER;
+            }
+            const string = parameters[category];
+            if (string.indexOf('#') !== -1) {
+                return string.replace('#', StringUtil.formatNumeric(value));
+            }
+            return string;
+        },
+        /**
+         * `f` is the fractional number as a whole number (1.234 yields 234)
+         */
+        getF: function (n) {
+            const tmp = n.toString();
+            const pos = tmp.indexOf('.');
+            if (pos === -1) {
+                return 0;
+            }
+            return parseInt(tmp.substr(pos + 1), 10);
+        },
+        /**
+         * `v` represents the number of digits of the fractional part (1.234 yields 3)
+         */
+        getV: function (n) {
+            return n.toString().replace(/^[^.]*\.?/, '').length;
+        },
+        // Afrikaans
+        af: function (n) {
+            if (n == 1)
+                return PLURAL_ONE;
+        },
+        // Amharic
+        am: function (n) {
+            const i = Math.floor(Math.abs(n));
+            if (n == 1 || i === 0)
+                return PLURAL_ONE;
+        },
+        // Arabic
+        ar: function (n) {
+            if (n == 0)
+                return PLURAL_ZERO;
+            if (n == 1)
+                return PLURAL_ONE;
+            if (n == 2)
+                return PLURAL_TWO;
+            const mod100 = n % 100;
+            if (mod100 >= 3 && mod100 <= 10)
+                return PLURAL_FEW;
+            if (mod100 >= 11 && mod100 <= 99)
+                return PLURAL_MANY;
+        },
+        // Assamese
+        as: function (n) {
+            const i = Math.floor(Math.abs(n));
+            if (n == 1 || i === 0)
+                return PLURAL_ONE;
+        },
+        // Azerbaijani
+        az: function (n) {
+            if (n == 1)
+                return PLURAL_ONE;
+        },
+        // Belarusian
+        be: function (n) {
+            const mod10 = n % 10;
+            const mod100 = n % 100;
+            if (mod10 == 1 && mod100 != 11)
+                return PLURAL_ONE;
+            if (mod10 >= 2 && mod10 <= 4 && !(mod100 >= 12 && mod100 <= 14))
+                return PLURAL_FEW;
+            if (mod10 == 0 || (mod10 >= 5 && mod10 <= 9) || (mod100 >= 11 && mod100 <= 14))
+                return PLURAL_MANY;
+        },
+        // Bulgarian
+        bg: function (n) {
+            if (n == 1)
+                return PLURAL_ONE;
+        },
+        // Bengali
+        bn: function (n) {
+            const i = Math.floor(Math.abs(n));
+            if (n == 1 || i === 0)
+                return PLURAL_ONE;
+        },
+        // Tibetan
+        bo: function (n) {
+        },
+        // Bosnian
+        bs: function (n) {
+            const v = this.getV(n);
+            const f = this.getF(n);
+            const mod10 = n % 10;
+            const mod100 = n % 100;
+            const fMod10 = f % 10;
+            const fMod100 = f % 100;
+            if ((v == 0 && mod10 == 1 && mod100 != 11) || (fMod10 == 1 && fMod100 != 11))
+                return PLURAL_ONE;
+            if ((v == 0 && mod10 >= 2 && mod10 <= 4 && mod100 >= 12 && mod100 <= 14)
+                || (fMod10 >= 2 && fMod10 <= 4 && fMod100 >= 12 && fMod100 <= 14))
+                return PLURAL_FEW;
+        },
+        // Czech
+        cs: function (n) {
+            const v = this.getV(n);
+            if (n == 1 && v === 0)
+                return PLURAL_ONE;
+            if (n >= 2 && n <= 4 && v === 0)
+                return PLURAL_FEW;
+            if (v === 0)
+                return PLURAL_MANY;
+        },
+        // Welsh
+        cy: function (n) {
+            if (n == 0)
+                return PLURAL_ZERO;
+            if (n == 1)
+                return PLURAL_ONE;
+            if (n == 2)
+                return PLURAL_TWO;
+            if (n == 3)
+                return PLURAL_FEW;
+            if (n == 6)
+                return PLURAL_MANY;
+        },
+        // Danish
+        da: function (n) {
+            if (n > 0 && n < 2)
+                return PLURAL_ONE;
+        },
+        // Greek
+        el: function (n) {
+            if (n == 1)
+                return PLURAL_ONE;
+        },
+        // Catalan (ca)
+        // German (de)
+        // English (en)
+        // Estonian (et)
+        // Finnish (fi)
+        // Italian (it)
+        // Dutch (nl)
+        // Swedish (sv)
+        // Swahili (sw)
+        // Urdu (ur)
+        en: function (n) {
+            if (n == 1 && this.getV(n) === 0)
+                return PLURAL_ONE;
+        },
+        // Spanish
+        es: function (n) {
+            if (n == 1)
+                return PLURAL_ONE;
+        },
+        // Basque
+        eu: function (n) {
+            if (n == 1)
+                return PLURAL_ONE;
+        },
+        // Persian
+        fa: function (n) {
+            if (n >= 0 && n <= 1)
+                return PLURAL_ONE;
+        },
+        // French
+        fr: function (n) {
+            if (n >= 0 && n < 2)
+                return PLURAL_ONE;
+        },
+        // Irish
+        ga: function (n) {
+            if (n == 1)
+                return PLURAL_ONE;
+            if (n == 2)
+                return PLURAL_TWO;
+            if (n == 3 || n == 4 || n == 5 || n == 6)
+                return PLURAL_FEW;
+            if (n == 7 || n == 8 || n == 9 || n == 10)
+                return PLURAL_MANY;
+        },
+        // Gujarati
+        gu: function (n) {
+            if (n >= 0 && n <= 1)
+                return PLURAL_ONE;
+        },
+        // Hebrew
+        he: function (n) {
+            const v = this.getV(n);
+            if (n == 1 && v === 0)
+                return PLURAL_ONE;
+            if (n == 2 && v === 0)
+                return PLURAL_TWO;
+            if (n > 10 && v === 0 && n % 10 == 0)
+                return PLURAL_MANY;
+        },
+        // Hindi
+        hi: function (n) {
+            if (n >= 0 && n <= 1)
+                return PLURAL_ONE;
+        },
+        // Croatian
+        hr: function (n) {
+            // same as Bosnian
+            return this.bs(n);
+        },
+        // Hungarian
+        hu: function (n) {
+            if (n == 1)
+                return PLURAL_ONE;
+        },
+        // Armenian
+        hy: function (n) {
+            if (n >= 0 && n < 2)
+                return PLURAL_ONE;
+        },
+        // Indonesian
+        id: function (n) {
+        },
+        // Icelandic
+        is: function (n) {
+            const f = this.getF(n);
+            if (f === 0 && n % 10 === 1 && !(n % 100 === 11) || !(f === 0))
+                return PLURAL_ONE;
+        },
+        // Japanese
+        ja: function (n) {
+        },
+        // Javanese
+        jv: function (n) {
+        },
+        // Georgian
+        ka: function (n) {
+            if (n == 1)
+                return PLURAL_ONE;
+        },
+        // Kazakh
+        kk: function (n) {
+            if (n == 1)
+                return PLURAL_ONE;
+        },
+        // Khmer
+        km: function (n) {
+        },
+        // Kannada
+        kn: function (n) {
+            if (n >= 0 && n <= 1)
+                return PLURAL_ONE;
+        },
+        // Korean
+        ko: function (n) {
+        },
+        // Kurdish
+        ku: function (n) {
+            if (n == 1)
+                return PLURAL_ONE;
+        },
+        // Kyrgyz
+        ky: function (n) {
+            if (n == 1)
+                return PLURAL_ONE;
+        },
+        // Luxembourgish
+        lb: function (n) {
+            if (n == 1)
+                return PLURAL_ONE;
+        },
+        // Lao
+        lo: function (n) {
+        },
+        // Lithuanian
+        lt: function (n) {
+            const mod10 = n % 10;
+            const mod100 = n % 100;
+            if (mod10 == 1 && !(mod100 >= 11 && mod100 <= 19))
+                return PLURAL_ONE;
+            if (mod10 >= 2 && mod10 <= 9 && !(mod100 >= 11 && mod100 <= 19))
+                return PLURAL_FEW;
+            if (this.getF(n) != 0)
+                return PLURAL_MANY;
+        },
+        // Latvian
+        lv: function (n) {
+            const mod10 = n % 10;
+            const mod100 = n % 100;
+            const v = this.getV(n);
+            const f = this.getF(n);
+            const fMod10 = f % 10;
+            const fMod100 = f % 100;
+            if (mod10 == 0 || (mod100 >= 11 && mod100 <= 19) || (v == 2 && fMod100 >= 11 && fMod100 <= 19))
+                return PLURAL_ZERO;
+            if ((mod10 == 1 && mod100 != 11) || (v == 2 && fMod10 == 1 && fMod100 != 11) || (v != 2 && fMod10 == 1))
+                return PLURAL_ONE;
+        },
+        // Macedonian
+        mk: function (n) {
+            return this.bs(n);
+        },
+        // Malayalam
+        ml: function (n) {
+            if (n == 1)
+                return PLURAL_ONE;
+        },
+        // Mongolian 
+        mn: function (n) {
+            if (n == 1)
+                return PLURAL_ONE;
+        },
+        // Marathi 
+        mr: function (n) {
+            if (n == 1)
+                return PLURAL_ONE;
+        },
+        // Malay 
+        ms: function (n) {
+        },
+        // Maltese 
+        mt: function (n) {
+            const mod100 = n % 100;
+            if (n == 1)
+                return PLURAL_ONE;
+            if (n == 0 || (mod100 >= 2 && mod100 <= 10))
+                return PLURAL_FEW;
+            if (mod100 >= 11 && mod100 <= 19)
+                return PLURAL_MANY;
+        },
+        // Burmese
+        my: function (n) {
+        },
+        // Norwegian
+        no: function (n) {
+            if (n == 1)
+                return PLURAL_ONE;
+        },
+        // Nepali
+        ne: function (n) {
+            if (n == 1)
+                return PLURAL_ONE;
+        },
+        // Odia
+        or: function (n) {
+            if (n == 1)
+                return PLURAL_ONE;
+        },
+        // Punjabi
+        pa: function (n) {
+            if (n == 1 || n == 0)
+                return PLURAL_ONE;
+        },
+        // Polish
+        pl: function (n) {
+            const v = this.getV(n);
+            const mod10 = n % 10;
+            const mod100 = n % 100;
+            if (n == 1 && v == 0)
+                return PLURAL_ONE;
+            if (v == 0 && mod10 >= 2 && mod10 <= 4 && !(mod100 >= 12 && mod100 <= 14))
+                return PLURAL_FEW;
+            if (v == 0 && ((n != 1 && mod10 >= 0 && mod10 <= 1) || (mod10 >= 5 && mod10 <= 9) || (mod100 >= 12 && mod100 <= 14)))
+                return PLURAL_MANY;
+        },
+        // Pashto
+        ps: function (n) {
+            if (n == 1)
+                return PLURAL_ONE;
+        },
+        // Portuguese
+        pt: function (n) {
+            if (n >= 0 && n < 2)
+                return PLURAL_ONE;
+        },
+        // Romanian
+        ro: function (n) {
+            const v = this.getV(n);
+            const mod100 = n % 100;
+            if (n == 1 && v === 0)
+                return PLURAL_ONE;
+            if (v != 0 || n == 0 || (mod100 >= 2 && mod100 <= 19))
+                return PLURAL_FEW;
+        },
+        // Russian
+        ru: function (n) {
+            const mod10 = n % 10;
+            const mod100 = n % 100;
+            if (this.getV(n) == 0) {
+                if (mod10 == 1 && mod100 != 11)
+                    return PLURAL_ONE;
+                if (mod10 >= 2 && mod10 <= 4 && !(mod100 >= 12 && mod100 <= 14))
+                    return PLURAL_FEW;
+                if (mod10 == 0 || (mod10 >= 5 && mod10 <= 9) || (mod100 >= 11 && mod100 <= 14))
+                    return PLURAL_MANY;
+            }
+        },
+        // Sindhi
+        sd: function (n) {
+            if (n == 1)
+                return PLURAL_ONE;
+        },
+        // Sinhala
+        si: function (n) {
+            if (n == 0 || n == 1 || (Math.floor(n) == 0 && this.getF(n) == 1))
+                return PLURAL_ONE;
+        },
+        // Slovak
+        sk: function (n) {
+            // same as Czech
+            return this.cs(n);
+        },
+        // Slovenian
+        sl: function (n) {
+            const v = this.getV(n);
+            const mod100 = n % 100;
+            if (v == 0 && mod100 == 1)
+                return PLURAL_ONE;
+            if (v == 0 && mod100 == 2)
+                return PLURAL_TWO;
+            if ((v == 0 && (mod100 == 3 || mod100 == 4)) || v != 0)
+                return PLURAL_FEW;
+        },
+        // Albanian
+        sq: function (n) {
+            if (n == 1)
+                return PLURAL_ONE;
+        },
+        // Serbian
+        sr: function (n) {
+            // same as Bosnian
+            return this.bs(n);
+        },
+        // Tamil
+        ta: function (n) {
+            if (n == 1)
+                return PLURAL_ONE;
+        },
+        // Telugu
+        te: function (n) {
+            if (n == 1)
+                return PLURAL_ONE;
+        },
+        // Tajik
+        tg: function (n) {
+        },
+        // Thai
+        th: function (n) {
+        },
+        // Turkmen
+        tk: function (n) {
+            if (n == 1)
+                return PLURAL_ONE;
+        },
+        // Turkish
+        tr: function (n) {
+            if (n == 1)
+                return PLURAL_ONE;
+        },
+        // Uyghur
+        ug: function (n) {
+            if (n == 1)
+                return PLURAL_ONE;
+        },
+        // Ukrainian
+        uk: function (n) {
+            // same as Russian
+            return this.ru(n);
+        },
+        // Uzbek
+        uz: function (n) {
+            if (n == 1)
+                return PLURAL_ONE;
+        },
+        // Vietnamese
+        vi: function (n) {
+        },
+        // Chinese
+        zh: function (n) {
+        },
+    };
+    return Plural;
 });
index 3cfd84460715a6e790b847cf62ea4463d309d1a5..09fe230c190bc1f1797b132ac548a29aa2058163 100644 (file)
@@ -1,79 +1,59 @@
-/**
- * Manages language items.
- * 
- * @author     Tim Duesterhus
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     Language (alias)
- * @module     WoltLabSuite/Core/Language
- */
-define(['Dictionary', './Template'], function(Dictionary, Template) {
-       "use strict";
-       
-       var _languageItems = new Dictionary();
-       
-       /**
-        * @exports     WoltLabSuite/Core/Language
-        */
-       var Language = {
-               /**
-                * Adds all the language items in the given object to the store.
-                * 
-                * @param       {Object.<string, string>}       object
-                */
-               addObject: function(object) {
-                       _languageItems.merge(Dictionary.fromObject(object));
-               },
-               
-               /**
-                * Adds a single language item to the store.
-                * 
-                * @param       {string}        key
-                * @param       {string}        value
-                */
-               add: function(key, value) {
-                       _languageItems.set(key, value);
-               },
-               
-               /**
-                * Fetches the language item specified by the given key.
-                * If the language item is a string it will be evaluated as
-                * WoltLabSuite/Core/Template with the given parameters.
-                * 
-                * @param       {string}        key             Language item to return.
-                * @param       {Object=}       parameters      Parameters to provide to WoltLabSuite/Core/Template.
-                * @return      {string}
-                */
-               get: function(key, parameters) {
-                       if (!parameters) parameters = { };
-                       
-                       var value = _languageItems.get(key);
-                       
-                       if (value === undefined) {
-                               return key;
-                       }
-                       
-                       // fetch Template, as it cannot be provided because of a circular dependency
-                       if (Template === undefined) Template = require('WoltLabSuite/Core/Template');
-                       
-                       if (typeof value === 'string') {
-                               // lazily convert to WCF.Template
-                               try {
-                                       _languageItems.set(key, new Template(value));
-                               }
-                               catch (e) {
-                                       _languageItems.set(key, new Template('{literal}' + value.replace(/\{\/literal\}/g, '{/literal}{ldelim}/literal}{literal}') + '{/literal}'));
-                               }
-                               value = _languageItems.get(key);
-                       }
-                       
-                       if (value instanceof Template) {
-                               value = value.fetch(parameters);
-                       }
-                       
-                       return value;
-               }
-       };
-       
-       return Language;
+var __importDefault = (this && this.__importDefault) || function (mod) {
+    return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+define(["require", "exports", "./Dictionary", "./Template"], function (require, exports, Dictionary_1, Template_1) {
+    "use strict";
+    Object.defineProperty(exports, "__esModule", { value: true });
+    exports.get = exports.add = exports.addObject = void 0;
+    Dictionary_1 = __importDefault(Dictionary_1);
+    Template_1 = __importDefault(Template_1);
+    const _languageItems = new Dictionary_1.default();
+    /**
+     * Adds all the language items in the given object to the store.
+     */
+    function addObject(object) {
+        _languageItems.merge(Dictionary_1.default.fromObject(object));
+    }
+    exports.addObject = addObject;
+    /**
+     * Adds a single language item to the store.
+     */
+    function add(key, value) {
+        _languageItems.set(key, value);
+    }
+    exports.add = add;
+    /**
+     * Fetches the language item specified by the given key.
+     * If the language item is a string it will be evaluated as
+     * WoltLabSuite/Core/Template with the given parameters.
+     *
+     * @param  {string}  key    Language item to return.
+     * @param  {Object=}  parameters  Parameters to provide to WoltLabSuite/Core/Template.
+     * @return  {string}
+     */
+    function get(key, parameters) {
+        let value = _languageItems.get(key);
+        if (value === undefined) {
+            return key;
+        }
+        // fetch Template, as it cannot be provided because of a circular dependency
+        if (Template_1.default === undefined) { //@ts-ignore
+            Template_1.default = require('./Template');
+        }
+        if (typeof value === 'string') {
+            // lazily convert to WCF.Template
+            try {
+                _languageItems.set(key, new Template_1.default(value));
+            }
+            catch (e) {
+                _languageItems.set(key, new Template_1.default('{literal}' + value.replace(/{\/literal}/g, '{/literal}{ldelim}/literal}{literal}') + '{/literal}'));
+            }
+            value = _languageItems.get(key);
+        }
+        if (value instanceof Template_1.default) {
+            value = value.fetch(parameters || {});
+        }
+        return value;
+    }
+    exports.get = get;
 });
index 3aac39f4a83859f43e5422677efeb15902fa6ec1..6e2dfdf96d58edd7760021e164c43aa8e4c630de 100644 (file)
-/**
- * List implementation relying on an array or if supported on a Set to hold values.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     List (alias)
- * @module     WoltLabSuite/Core/List
- */
-define([], function() {
-       "use strict";
-       
-       var _hasSet = objOwns(window, 'Set') && typeof window.Set === 'function';
-       
-       /**
-        * @constructor
-        */
-       function List() {
-               this._set = (_hasSet) ? new Set() : [];
-       }
-       List.prototype = {
-               /**
-                * Appends an element to the list, silently rejects adding an already existing value.
-                * 
-                * @param       {?}     value   unique element
-                */
-               add: function(value) {
-                       if (_hasSet) {
-                               this._set.add(value);
-                       }
-                       else if (!this.has(value)) {
-                               this._set.push(value);
-                       }
-               },
-               
-               /**
-                * Removes all elements from the list.
-                */
-               clear: function() {
-                       if (_hasSet) {
-                               this._set.clear();
-                       }
-                       else {
-                               this._set = [];
-                       }
-               },
-               
-               /**
-                * Removes an element from the list, returns true if the element was in the list.
-                * 
-                * @param       {?}             value   element
-                * @return      {boolean}       true if element was in the list
-                */
-               'delete': function(value) {
-                       if (_hasSet) {
-                               return this._set['delete'](value);
-                       }
-                       else {
-                               var index = this._set.indexOf(value);
-                               if (index === -1) {
-                                       return false;
-                               }
-                               
-                               this._set.splice(index, 1);
-                               return true;
-                       }
-               },
-               
-               /**
-                * Calls `callback` for each element in the list.
-                */
-               forEach: function(callback) {
-                       if (_hasSet) {
-                               this._set.forEach(callback);
-                       }
-                       else {
-                               for (var i = 0, length = this._set.length; i < length; i++) {
-                                       callback(this._set[i]);
-                               }
-                       }
-               },
-               
-               /**
-                * Returns true if the list contains the element.
-                * 
-                * @param       {?}             value   element
-                * @return      {boolean}       true if element is in the list
-                */
-               has: function(value) {
-                       if (_hasSet) {
-                               return this._set.has(value);
-                       }
-                       else {
-                               return (this._set.indexOf(value) !== -1);
-                       }
-               }
-       };
-       
-       Object.defineProperty(List.prototype, 'size', {
-               enumerable: false,
-               configurable: true,
-               get: function() {
-                       if (_hasSet) {
-                               return this._set.size;
-                       }
-                       else {
-                               return this._set.length;
-                       }
-               }
-       });
-       
-       return List;
+define(["require", "exports"], function (require, exports) {
+    "use strict";
+    /**
+     * List implementation relying on an array or if supported on a Set to hold values.
+     *
+     * @author  Alexander Ebert
+     * @copyright  2001-2019 WoltLab GmbH
+     * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+     * @module  List (alias)
+     * @module  WoltLabSuite/Core/List
+     */
+    /**
+     * @constructor
+     */
+    class List {
+        constructor() {
+            this._set = new Set();
+        }
+        /**
+         * Appends an element to the list, silently rejects adding an already existing value.
+         */
+        add(value) {
+            this._set.add(value);
+        }
+        /**
+         * Removes all elements from the list.
+         */
+        clear() {
+            this._set.clear();
+        }
+        /**
+         * Removes an element from the list, returns true if the element was in the list.
+         */
+        delete(value) {
+            return this._set.delete(value);
+        }
+        /**
+         * Invokes the `callback` for each element in the list.
+         */
+        forEach(callback) {
+            this._set.forEach(callback);
+        }
+        /**
+         * Returns true if the list contains the element.
+         */
+        has(value) {
+            return this._set.has(value);
+        }
+        get size() {
+            return this._set.size;
+        }
+    }
+    return List;
 });
index 06b97743d24f7794796a1ba70ea2934962a7f12e..35fc6216044b9600540a5387c64f7811d4ba84bf 100644 (file)
@@ -1,48 +1,37 @@
 /**
  * Provides helper functions for Number handling.
- * 
- * @author     Tim Duesterhus
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/NumberUtil
+ *
+ * @author  Tim Duesterhus
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module  WoltLabSuite/Core/NumberUtil
  */
-define([], function() {
-       "use strict";
-       
-       /**
-        * @exports     WoltLabSuite/Core/NumberUtil
-        */
-       var NumberUtil = {
-               /**
-                * Decimal adjustment of a number.
-                *
-                * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
-                * @param       {Number}        value   The number.
-                * @param       {Integer}       exp     The exponent (the 10 logarithm of the adjustment base).
-                * @returns     {Number}        The adjusted value.
-                */
-               round: function (value, exp) {
-                       // If the exp is undefined or zero...
-                       if (typeof exp === 'undefined' || +exp === 0) {
-                               return Math.round(value);
-                       }
-                       value = +value;
-                       exp = +exp;
-                       
-                       // If the value is not a number or the exp is not an integer...
-                       if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) {
-                               return NaN;
-                       }
-                       
-                       // Shift
-                       value = value.toString().split('e');
-                       value = Math.round(+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp)));
-                       
-                       // Shift back
-                       value = value.toString().split('e');
-                       return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp));
-               }
-       };
-       
-       return NumberUtil;
+define(["require", "exports"], function (require, exports) {
+    "use strict";
+    Object.defineProperty(exports, "__esModule", { value: true });
+    exports.round = void 0;
+    /**
+     * Decimal adjustment of a number.
+     *
+     * @see  https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
+     */
+    function round(value, exp) {
+        // If the exp is undefined or zero...
+        if (typeof exp === 'undefined' || +exp === 0) {
+            return Math.round(value);
+        }
+        value = +value;
+        exp = +exp;
+        // If the value is not a number or the exp is not an integer...
+        if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) {
+            return NaN;
+        }
+        // Shift
+        let tmp = value.toString().split('e');
+        value = Math.round(+(tmp[0] + 'e' + (tmp[1] ? (+tmp[1] - exp) : -exp)));
+        // Shift back
+        tmp = value.toString().split('e');
+        return +(tmp[0] + 'e' + (tmp[1] ? (+tmp[1] + exp) : exp));
+    }
+    exports.round = round;
 });
index c499a5e31291ba69103a41faa843609d14c30ffa..8ba48413b0a8e23b6ad323cc8da4358a0c008eda 100644 (file)
 /**
- * Simple `object` to `object` map using a native WeakMap on supported browsers, otherwise a set of two arrays.
- * 
+ * Simple `object` to `object` map using a WeakMap.
+ *
  * If you're looking for a dictionary with string keys, please see `WoltLabSuite/Core/Dictionary`.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     ObjectMap (alias)
- * @module     WoltLabSuite/Core/ObjectMap
+ *
+ * @author  Alexander Ebert
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module  ObjectMap (alias)
+ * @module  WoltLabSuite/Core/ObjectMap
  */
-define([], function() {
-       "use strict";
-       
-       var _hasMap = objOwns(window, 'WeakMap') && typeof window.WeakMap === 'function';
-       
-       /**
-        * @constructor
-        */
-       function ObjectMap() {
-               this._map = (_hasMap) ? new WeakMap() : { key: [], value: [] };
-       }
-       ObjectMap.prototype = {
-               /**
-                * Sets a new key with given value, will overwrite an existing key.
-                * 
-                * @param       {object}        key     key
-                * @param       {object}        value   value
-                */
-               set: function(key, value) {
-                       if (typeof key !== 'object' || key === null) {
-                               throw new TypeError("Only objects can be used as key");
-                       }
-                       
-                       if (typeof value !== 'object' || value === null) {
-                               throw new TypeError("Only objects can be used as value");
-                       }
-                       
-                       if (_hasMap) {
-                               this._map.set(key, value);
-                       }
-                       else {
-                               this._map.key.push(key);
-                               this._map.value.push(value);
-                       }
-               },
-               
-               /**
-                * Removes a key from the map.
-                * 
-                * @param       {object}        key     key
-                */
-               'delete': function(key) {
-                       if (_hasMap) {
-                               this._map['delete'](key);
-                       }
-                       else {
-                               var index = this._map.key.indexOf(key);
-                               this._map.key.splice(index);
-                               this._map.value.splice(index);
-                       }
-               },
-               
-               /**
-                * Returns true if dictionary contains a value for given key.
-                * 
-                * @param       {object}        key     key
-                * @return      {boolean}       true if key exists
-                */
-               has: function(key) {
-                       if (_hasMap) {
-                               return this._map.has(key);
-                       }
-                       else {
-                               return (this._map.key.indexOf(key) !== -1);
-                       }
-               },
-               
-               /**
-                * Retrieves a value by key, returns undefined if there is no match.
-                * 
-                * @param       {object}        key     key
-                * @return      {*}
-                */
-               get: function(key) {
-                       if (_hasMap) {
-                               return this._map.get(key);
-                       }
-                       else {
-                               var index = this._map.key.indexOf(key);
-                               if (index !== -1) {
-                                       return this._map.value[index];
-                               }
-                               
-                               return undefined;
-                       }
-               }
-       };
-       
-       return ObjectMap;
+define(["require", "exports"], function (require, exports) {
+    "use strict";
+    class ObjectMap {
+        constructor() {
+            this._map = new WeakMap();
+        }
+        /**
+         * Sets a new key with given value, will overwrite an existing key.
+         */
+        set(key, value) {
+            if (typeof key !== 'object' || key === null) {
+                throw new TypeError('Only objects can be used as key');
+            }
+            if (typeof value !== 'object' || value === null) {
+                throw new TypeError('Only objects can be used as value');
+            }
+            this._map.set(key, value);
+        }
+        /**
+         * Removes a key from the map.
+         */
+        delete(key) {
+            this._map.delete(key);
+        }
+        /**
+         * Returns true if dictionary contains a value for given key.
+         */
+        has(key) {
+            return this._map.has(key);
+        }
+        /**
+         * Retrieves a value by key, returns undefined if there is no match.
+         */
+        get(key) {
+            return this._map.get(key);
+        }
+    }
+    return ObjectMap;
 });
index a05fa777603c29b90993b53ffbc369565dadcb6b..a93125afe5555c4d47f3eeaa80d62f3b23fa9fff 100644 (file)
@@ -1,62 +1,48 @@
 /**
  * Manages user permissions.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     Permission (alias)
- * @module     WoltLabSuite/Core/Permission
+ *
+ * @author  Matthias Schmidt
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module  Permission (alias)
+ * @module  WoltLabSuite/Core/Permission
  */
-define(['Dictionary'], function(Dictionary) {
-       "use strict";
-       
-       var _permissions = new Dictionary();
-       
-       /**
-        * @exports     WoltLabSuite/Core/Permission
-        */
-       return {
-               /**
-                * Adds a single permission to the store.
-                * 
-                * @param       {string}        permission      permission name
-                * @param       {boolean}       value           permission value
-                */
-               add: function(permission, value) {
-                       if (typeof value !== "boolean") {
-                               throw new TypeError("Permission value has to be boolean.");
-                       }
-                       
-                       _permissions.set(permission, value);
-               },
-               
-               /**
-                * Adds all the permissions in the given object to the store.
-                * 
-                * @param       {Object.<string, boolean>}      object          permission list
-                */
-               addObject: function(object) {
-                       for (var key in object) {
-                               if (objOwns(object, key)) {
-                                       this.add(key, object[key]);
-                               }
-                       }
-               },
-               
-               /**
-                * Returns the value of a permission.
-                * 
-                * If the permission is unknown, false is returned.
-                * 
-                * @param       {string}        permission      permission name
-                * @return      {boolean}       permission value
-                */
-               get: function(permission) {
-                       if (_permissions.has(permission)) {
-                               return _permissions.get(permission);
-                       }
-                       
-                       return false;
-               }
-       };
+define(["require", "exports"], function (require, exports) {
+    "use strict";
+    Object.defineProperty(exports, "__esModule", { value: true });
+    exports.get = exports.addObject = exports.add = void 0;
+    const _permissions = new Map();
+    /**
+     * Adds a single permission to the store.
+     */
+    function add(permission, value) {
+        if (typeof value !== 'boolean') {
+            throw new TypeError('The permission value has to be boolean.');
+        }
+        _permissions.set(permission, value);
+    }
+    exports.add = add;
+    /**
+     * Adds all the permissions in the given object to the store.
+     */
+    function addObject(object) {
+        for (const key in object) {
+            if (object.hasOwnProperty(key)) {
+                this.add(key, object[key]);
+            }
+        }
+    }
+    exports.addObject = addObject;
+    /**
+     * Returns the value of a permission.
+     *
+     * If the permission is unknown, false is returned.
+     */
+    function get(permission) {
+        if (_permissions.has(permission)) {
+            return _permissions.get(permission);
+        }
+        return false;
+    }
+    exports.get = get;
 });
index 094aca18e72dad1f6a784f8eb0c9af61f4e39026..9e40f6d42c2268340bf438f2aa93f6267fd75e5b 100644 (file)
-/**
- * Provides helper functions for String handling.
- * 
- * @author     Tim Duesterhus, Joshua Ruesweg
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     StringUtil (alias)
- * @module     WoltLabSuite/Core/StringUtil
- */
-define(['Language', './NumberUtil'], function(Language, NumberUtil) {
-       "use strict";
-       
-       /**
-        * @exports     WoltLabSuite/Core/StringUtil
-        */
-       return {
-               /**
-                * Adds thousands separators to a given number.
-                * 
-                * @see         http://stackoverflow.com/a/6502556/782822
-                * @param       {?}     number
-                * @return      {String}
-                */
-               addThousandsSeparator: function(number) {
-                       // Fetch Language, as it cannot be provided because of a circular dependency
-                       if (Language === undefined) Language = require('Language');
-                       
-                       return String(number).replace(/(^-?\d{1,3}|\d{3})(?=(?:\d{3})+(?:$|\.))/g, '$1' + Language.get('wcf.global.thousandsSeparator'));
-               },
-               
-               /**
-                * Escapes special HTML-characters within a string
-                * 
-                * @param       {?}     string
-                * @return      {String}
-                */
-               escapeHTML: function (string) {
-                       return String(string).replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
-               },
-               
-               /**
-                * Escapes a String to work with RegExp.
-                * 
-                * @see         https://github.com/sstephenson/prototype/blob/master/src/prototype/lang/regexp.js#L25
-                * @param       {?}     string
-                * @return      {String}
-                */
-               escapeRegExp: function(string) {
-                       return String(string).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
-               },
-               
-               /**
-                * Rounds number to given count of floating point digits, localizes decimal-point and inserts thousands separators.
-                * 
-                * @param       {?}             number
-                * @param       {int}           decimalPlaces   The number of decimal places to leave after rounding.
-                * @return      {String}
-                */
-               formatNumeric: function(number, decimalPlaces) {
-                       // Fetch Language, as it cannot be provided because of a circular dependency
-                       if (Language === undefined) Language = require('Language');
-                       
-                       number = String(NumberUtil.round(number, decimalPlaces || -2));
-                       var numberParts = number.split('.');
-                       
-                       number = this.addThousandsSeparator(numberParts[0]);
-                       if (numberParts.length > 1) number += Language.get('wcf.global.decimalPoint') + numberParts[1];
-                       
-                       number = number.replace('-', '\u2212');
-                       
-                       return number;
-               },
-               
-               /**
-                * Makes a string's first character lowercase.
-                * 
-                * @param       {?}             string
-                * @return      {String}
-                */
-               lcfirst: function(string) {
-                       return String(string).substring(0, 1).toLowerCase() + string.substring(1);
-               },
-               
-               /**
-                * Makes a string's first character uppercase.
-                * 
-                * @param       {?}             string
-                * @return      {String}
-                */
-               ucfirst: function(string) {
-                       return String(string).substring(0, 1).toUpperCase() + string.substring(1);
-               },
-               
-               /**
-                * Unescapes special HTML-characters within a string.
-                * 
-                * @param       {?}             string
-                * @return      {String}
-                */
-               unescapeHTML: function(string) {
-                       return String(string).replace(/&amp;/g, '&').replace(/&quot;/g, '"').replace(/&lt;/g, '<').replace(/&gt;/g, '>');
-               },
-               
-               /**
-                * Shortens numbers larger than 1000 by using unit suffixes.
-                *
-                * @param       {?}             number
-                * @return      {String}
-                */
-               shortUnit: function(number) {
-                       var unitSuffix = '';
-                       
-                       if (number >= 1000000) {
-                               number /= 1000000;
-                               
-                               if (number > 10) {
-                                       number = Math.floor(number);
-                               }
-                               else {
-                                       number = NumberUtil.round(number, -1);
-                               }
-                               
-                               unitSuffix = 'M';
-                       }
-                       else if (number >= 1000) {
-                               number /= 1000;
-                               
-                               if (number > 10) {
-                                       number = Math.floor(number);
-                               }
-                               else {
-                                       number = NumberUtil.round(number, -1);
-                               }
-                               
-                               unitSuffix = 'k';
-                       }
-                       
-                       return this.formatNumeric(number) + unitSuffix;
-               }
-       };
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    o[k2] = m[k];
+}));
+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
+    Object.defineProperty(o, "default", { enumerable: true, value: v });
+}) : function(o, v) {
+    o["default"] = v;
+});
+var __importStar = (this && this.__importStar) || function (mod) {
+    if (mod && mod.__esModule) return mod;
+    var result = {};
+    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+    __setModuleDefault(result, mod);
+    return result;
+};
+define(["require", "exports", "./Language", "./NumberUtil"], function (require, exports, Language, NumberUtil) {
+    "use strict";
+    Object.defineProperty(exports, "__esModule", { value: true });
+    exports.shortUnit = exports.unescapeHTML = exports.ucfirst = exports.lcfirst = exports.formatNumeric = exports.escapeRegExp = exports.escapeHTML = exports.addThousandsSeparator = void 0;
+    Language = __importStar(Language);
+    NumberUtil = __importStar(NumberUtil);
+    /**
+     * Adds thousands separators to a given number.
+     *
+     * @see    http://stackoverflow.com/a/6502556/782822
+     */
+    function addThousandsSeparator(number) {
+        // Fetch Language, as it cannot be provided because of a circular dependency
+        if (Language === undefined) { //@ts-ignore
+            Language = require('./Language');
+        }
+        return String(number).replace(/(^-?\d{1,3}|\d{3})(?=(?:\d{3})+(?:$|\.))/g, '$1' + Language.get('wcf.global.thousandsSeparator'));
+    }
+    exports.addThousandsSeparator = addThousandsSeparator;
+    /**
+     * Escapes special HTML-characters within a string
+     */
+    function escapeHTML(string) {
+        return String(string).replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+    }
+    exports.escapeHTML = escapeHTML;
+    /**
+     * Escapes a String to work with RegExp.
+     *
+     * @see    https://github.com/sstephenson/prototype/blob/master/src/prototype/lang/regexp.js#L25
+     */
+    function escapeRegExp(string) {
+        return String(string).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
+    }
+    exports.escapeRegExp = escapeRegExp;
+    /**
+     * Rounds number to given count of floating point digits, localizes decimal-point and inserts thousands separators.
+     */
+    function formatNumeric(number, decimalPlaces) {
+        // Fetch Language, as it cannot be provided because of a circular dependency
+        if (Language === undefined) { //@ts-ignore
+            Language = require('./Language');
+        }
+        let tmp = NumberUtil.round(number, decimalPlaces || -2).toString();
+        const numberParts = tmp.split('.');
+        tmp = this.addThousandsSeparator(+numberParts[0]);
+        if (numberParts.length > 1)
+            tmp += Language.get('wcf.global.decimalPoint') + numberParts[1];
+        tmp = tmp.replace('-', '\u2212');
+        return tmp;
+    }
+    exports.formatNumeric = formatNumeric;
+    /**
+     * Makes a string's first character lowercase.
+     */
+    function lcfirst(string) {
+        return String(string).substring(0, 1).toLowerCase() + string.substring(1);
+    }
+    exports.lcfirst = lcfirst;
+    /**
+     * Makes a string's first character uppercase.
+     */
+    function ucfirst(string) {
+        return String(string).substring(0, 1).toUpperCase() + string.substring(1);
+    }
+    exports.ucfirst = ucfirst;
+    /**
+     * Unescapes special HTML-characters within a string.
+     */
+    function unescapeHTML(string) {
+        return String(string).replace(/&amp;/g, '&').replace(/&quot;/g, '"').replace(/&lt;/g, '<').replace(/&gt;/g, '>');
+    }
+    exports.unescapeHTML = unescapeHTML;
+    /**
+     * Shortens numbers larger than 1000 by using unit suffixes.
+     */
+    function shortUnit(number) {
+        let unitSuffix = '';
+        if (number >= 1000000) {
+            number /= 1000000;
+            if (number > 10) {
+                number = Math.floor(number);
+            }
+            else {
+                number = NumberUtil.round(number, -1);
+            }
+            unitSuffix = 'M';
+        }
+        else if (number >= 1000) {
+            number /= 1000;
+            if (number > 10) {
+                number = Math.floor(number);
+            }
+            else {
+                number = NumberUtil.round(number, -1);
+            }
+            unitSuffix = 'k';
+        }
+        return this.formatNumeric(number) + unitSuffix;
+    }
+    exports.shortUnit = shortUnit;
 });
index 1efce33b5ec94a61e96785bf23522d7b5b003efb..2169387bd93802f9e0a48b93c2a210c97f3b395e 100644 (file)
@@ -1,74 +1,85 @@
-/**
- * WoltLabSuite/Core/Template provides a template scripting compiler similar
- * to the PHP one of WoltLab Suite Core. It supports a limited
- * set of useful commands and compiles templates down to a pure
- * JavaScript Function.
- * 
- * @author     Tim Duesterhus
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Template
- */
-define(['./Template.grammar', './StringUtil', 'Language', 'WoltLabSuite/Core/I18n/Plural'], function(parser, StringUtil, Language, I18nPlural) {
-       "use strict";
-       
-       // work around bug in AMD module generation of Jison
-       function Parser() {
-               this.yy = {};
-       }
-       Parser.prototype = parser;
-       parser.Parser = Parser;
-       parser = new Parser();
-
-       /**
-        * Compiles the given template.
-        * 
-        * @param       {string}        template        Template to compile.
-        * @constructor
-        */
-       function Template(template) {
-               // Fetch Language/StringUtil, as it cannot be provided because of a circular dependency
-               if (Language === undefined) Language = require('Language');
-               if (StringUtil === undefined) StringUtil = require('StringUtil');
-               
-               try {
-                       template = parser.parse(template);
-                       template = "var tmp = {};\n"
-                       + "for (var key in v) tmp[key] = v[key];\n"
-                       + "v = tmp;\n"
-                       + "v.__wcf = window.WCF; v.__window = window;\n"
-                       + "return " + template;
-                       
-                       this.fetch = new Function("StringUtil", "Language", "I18nPlural", "v", template).bind(undefined, StringUtil, Language, I18nPlural);
-               }
-               catch (e) {
-                       console.debug(e.message);
-                       throw e;
-               }
-       }
-       
-       Object.defineProperty(Template, 'callbacks', {
-               enumerable: false,
-               configurable: false,
-               get: function() {
-                       throw new Error('WCF.Template.callbacks is no longer supported');
-               },
-               set: function(value) {
-                       throw new Error('WCF.Template.callbacks is no longer supported');
-               }
-       });
-       
-       Template.prototype = {
-               /**
-                * Evaluates the Template using the given parameters.
-                * 
-                * @param       {object}        v       Parameters to pass to the template.
-                */
-               fetch: function(v) {
-                       // this will be replaced in the init function
-                       throw new Error('This Template is not initialized.');
-               }
-       };
-       
-       return Template;
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    o[k2] = m[k];
+}));
+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
+    Object.defineProperty(o, "default", { enumerable: true, value: v });
+}) : function(o, v) {
+    o["default"] = v;
+});
+var __importStar = (this && this.__importStar) || function (mod) {
+    if (mod && mod.__esModule) return mod;
+    var result = {};
+    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+    __setModuleDefault(result, mod);
+    return result;
+};
+define(["require", "exports", "./Template.grammar", "./StringUtil", "./Language", "./I18n/Plural"], function (require, exports, parser, StringUtil, Language, I18nPlural) {
+    "use strict";
+    parser = __importStar(parser);
+    StringUtil = __importStar(StringUtil);
+    Language = __importStar(Language);
+    I18nPlural = __importStar(I18nPlural);
+    // @todo: still required?
+    // work around bug in AMD module generation of Jison
+    /*function Parser() {
+      this.yy = {};
+    }
+    
+    Parser.prototype = parser;
+    parser.Parser = Parser;
+    parser = new Parser();*/
+    /**
+     * Compiles the given template.
+     *
+     * @param  {string}  template  Template to compile.
+     * @constructor
+     */
+    class Template {
+        constructor(template) {
+            // Fetch Language/StringUtil, as it cannot be provided because of a circular dependency
+            if (Language === undefined) { //@ts-ignore
+                Language = require('./Language');
+            }
+            if (StringUtil === undefined) { //@ts-ignore
+                StringUtil = require('./StringUtil');
+            }
+            try {
+                template = parser.parse(template);
+                template = 'var tmp = {};\n'
+                    + 'for (var key in v) tmp[key] = v[key];\n'
+                    + 'v = tmp;\n'
+                    + 'v.__wcf = window.WCF; v.__window = window;\n'
+                    + 'return ' + template;
+                this.fetch = new Function('StringUtil', 'Language', 'I18nPlural', 'v', template).bind(undefined, StringUtil, Language, I18nPlural);
+            }
+            catch (e) {
+                console.debug(e.message);
+                throw e;
+            }
+        }
+        /**
+         * Evaluates the Template using the given parameters.
+         *
+         * @param  {object}  v  Parameters to pass to the template.
+         */
+        fetch(v) {
+            // this will be replaced in the init function
+            throw new Error('This Template is not initialized.');
+        }
+    }
+    Object.defineProperty(Template, 'callbacks', {
+        enumerable: false,
+        configurable: false,
+        get: function () {
+            throw new Error('WCF.Template.callbacks is no longer supported');
+        },
+        set: function (value) {
+            throw new Error('WCF.Template.callbacks is no longer supported');
+        },
+    });
+    return Template;
 });
index e81eb84808db03f351a95dea2c741159b38a7115..f6ad98c9bea12eac2f1389ba4ee196e7990ff800 100644 (file)
@@ -1,57 +1,38 @@
 /**
  * Provides data of the active user.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     User (alias)
- * @module     WoltLabSuite/Core/User
+ *
+ * @author  Matthias Schmidt
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module  User (alias)
+ * @module  WoltLabSuite/Core/User
  */
-define([], function() {
-       "use strict";
-       
-       var _didInit = false;
-       var _link;
-       
-       /**
-        * @exports     WoltLabSuite/Core/User
-        */
-       return {
-               /**
-                * Returns the link to the active user's profile or an empty string
-                * if the active user is a guest.
-                * 
-                * @return      {string}
-                */
-               getLink: function() {
-                       return _link;
-               },
-               
-               /**
-                * Initializes the user object.
-                * 
-                * @param       {int}           userId          id of the user, `0` for guests
-                * @param       {string}        username        name of the user, empty for guests
-                * @param       {string}        userLink        link to the user's profile, empty for guests
-                */
-               init: function(userId, username, userLink) {
-                       if (_didInit) {
-                               throw new Error('User has already been initialized.');
-                       }
-                       
-                       // define non-writeable properties for userId and username
-                       Object.defineProperty(this, 'userId', {
-                               value: userId,
-                               writable: false
-                       });
-                       Object.defineProperty(this, 'username', {
-                               value: username,
-                               writable: false
-                       });
-                       
-                       _link = userLink;
-                       
-                       _didInit = true;
-               }
-       };
+define(["require", "exports"], function (require, exports) {
+    "use strict";
+    let _link;
+    return {
+        /**
+         * Returns the link to the active user's profile or an empty string
+         * if the active user is a guest.
+         */
+        getLink: () => _link || '',
+        /**
+         * Initializes the user object.
+         */
+        init: (userId, username, link) => {
+            if (_link !== undefined) {
+                throw new Error('User has already been initialized.');
+            }
+            // define non-writeable properties for userId and username
+            Object.defineProperty(this, 'userId', {
+                value: userId,
+                writable: false,
+            });
+            Object.defineProperty(this, 'username', {
+                value: username,
+                writable: false,
+            });
+            _link = link || '';
+        },
+    };
 });
diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/ColorUtil.ts b/wcfsetup/install/files/ts/WoltLabSuite/Core/ColorUtil.ts
new file mode 100644 (file)
index 0000000..ffa60dd
--- /dev/null
@@ -0,0 +1,201 @@
+/**
+ * Helper functions to convert between different color formats.
+ *
+ * @author      Alexander Ebert
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module  ColorUtil (alias)
+ * @module      WoltLabSuite/Core/ColorUtil
+ */
+
+const ColorUtil = {
+  /**
+   * Converts a HSV color into RGB.
+   *
+   * @see  https://secure.wikimedia.org/wikipedia/de/wiki/HSV-Farbraum#Transformation_von_RGB_und_HSV
+   */
+  hsvToRgb: (h: number, s: number, v: number): RGB => {
+    const rgb: RGB = {r: 0, g: 0, b: 0};
+    let h2: number, f: number, p: number, q: number, t: number;
+
+    h2 = Math.floor(h / 60);
+    f = h / 60 - h2;
+
+    s /= 100;
+    v /= 100;
+
+    p = v * (1 - s);
+    q = v * (1 - s * f);
+    t = v * (1 - s * (1 - f));
+
+    if (s == 0) {
+      rgb.r = rgb.g = rgb.b = v;
+    } else {
+      switch (h2) {
+        case 1:
+          rgb.r = q;
+          rgb.g = v;
+          rgb.b = p;
+          break;
+
+        case 2:
+          rgb.r = p;
+          rgb.g = v;
+          rgb.b = t;
+          break;
+
+        case 3:
+          rgb.r = p;
+          rgb.g = q;
+          rgb.b = v;
+          break;
+
+        case 4:
+          rgb.r = t;
+          rgb.g = p;
+          rgb.b = v;
+          break;
+
+        case 5:
+          rgb.r = v;
+          rgb.g = p;
+          rgb.b = q;
+          break;
+
+        case 0:
+        case 6:
+          rgb.r = v;
+          rgb.g = t;
+          rgb.b = p;
+          break;
+      }
+    }
+
+    return {
+      r: Math.round(rgb.r * 255),
+      g: Math.round(rgb.g * 255),
+      b: Math.round(rgb.b * 255),
+    };
+  },
+
+  /**
+   * Converts a RGB color into HSV.
+   *
+   * @see  https://secure.wikimedia.org/wikipedia/de/wiki/HSV-Farbraum#Transformation_von_RGB_und_HSV
+   */
+  rgbToHsv: (r: number, g: number, b: number): HSV => {
+    let h: number, s: number, v: number;
+    let max: number, min: number, diff: number;
+
+    r /= 255;
+    g /= 255;
+    b /= 255;
+
+    max = Math.max(Math.max(r, g), b);
+    min = Math.min(Math.min(r, g), b);
+    diff = max - min;
+
+    h = 0;
+    if (max !== min) {
+      switch (max) {
+        case r:
+          h = 60 * ((g - b) / diff);
+          break;
+
+        case g:
+          h = 60 * (2 + (b - r) / diff);
+          break;
+
+        case b:
+          h = 60 * (4 + (r - g) / diff);
+          break;
+      }
+
+      if (h < 0) {
+        h += 360;
+      }
+    }
+
+    if (max === 0) {
+      s = 0;
+    } else {
+      s = diff / max;
+    }
+
+    v = max;
+
+    return {
+      h: Math.round(h),
+      s: Math.round(s * 100),
+      v: Math.round(v * 100),
+    };
+  },
+
+  /**
+   * Converts HEX into RGB.
+   */
+  hexToRgb: (hex: string): RGB | typeof Number.NaN => {
+    if (/^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(hex)) {
+      // only convert #abc and #abcdef
+      const parts = hex.split('');
+
+      // drop the hashtag
+      if (parts[0] === '#') {
+        parts.shift();
+      }
+
+      // parse shorthand #xyz
+      if (parts.length === 3) {
+        return {
+          r: parseInt(parts[0] + '' + parts[0], 16),
+          g: parseInt(parts[1] + '' + parts[1], 16),
+          b: parseInt(parts[2] + '' + parts[2], 16),
+        };
+      } else {
+        return {
+          r: parseInt(parts[0] + '' + parts[1], 16),
+          g: parseInt(parts[2] + '' + parts[3], 16),
+          b: parseInt(parts[4] + '' + parts[5], 16),
+        };
+      }
+    }
+
+    return Number.NaN;
+  },
+
+  /**
+   * Converts a RGB into HEX.
+   *
+   * @see  http://www.linuxtopia.org/online_books/javascript_guides/javascript_faq/rgbtohex.htm
+   */
+  rgbToHex: (r: number, g: number, b: number): string => {
+    const charList = '0123456789ABCDEF';
+
+    if (g === undefined) {
+      if (r.toString().match(/^rgba?\((\d+), ?(\d+), ?(\d+)(?:, ?[0-9.]+)?\)$/)) {
+        r = +RegExp.$1;
+        g = +RegExp.$2;
+        b = +RegExp.$3;
+      }
+    }
+
+    return (charList.charAt((r - r % 16) / 16) + '' + charList.charAt(r % 16)) + '' + (charList.charAt((g - g % 16) / 16) + '' + charList.charAt(g % 16)) + '' + (charList.charAt((b - b % 16) / 16) + '' + charList.charAt(b % 16));
+  },
+};
+
+interface RGB {
+  r: number;
+  g: number;
+  b: number;
+}
+
+interface HSV {
+  h: number;
+  s: number;
+  v: number;
+}
+
+// WCF.ColorPicker compatibility (color format conversion)
+window.__wcf_bc_colorUtil = ColorUtil;
+
+export = ColorUtil;
index ee0524bb97583755560e8b17ee00796996ee2ab9..37f50f12ea5d6fa80ec6ab92b73222206231e0e0 100644 (file)
@@ -31,8 +31,7 @@ const _cloneObject = function (obj: object | any[]): object | any[] | null {
   return newObj;
 };
 
-//noinspection JSUnresolvedVariable
-const _prefix = 'wsc1337' + /*window.WCF_PATH.hashCode()*/ +'-';
+const _prefix = 'wsc' + window.WCF_PATH.hashCode() +'-';
 
 /**
  * Deep clones an object.
diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Devtools.ts b/wcfsetup/install/files/ts/WoltLabSuite/Core/Devtools.ts
new file mode 100644 (file)
index 0000000..4b5d596
--- /dev/null
@@ -0,0 +1,99 @@
+/**
+ * Developer tools for WoltLab Suite.
+ *
+ * @author  Alexander Ebert
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module  Devtools (alias)
+ * @module  WoltLabSuite/Core/Devtools
+ */
+
+let _settings = {
+  editorAutosave: true,
+  eventLogging: false,
+};
+
+function _updateConfig() {
+  if (window.sessionStorage) {
+    window.sessionStorage.setItem('__wsc_devtools_config', JSON.stringify(_settings));
+  }
+}
+
+const Devtools = {
+  /**
+   * Prints the list of available commands.
+   */
+  help: (): void => {
+    window.console.log('');
+    window.console.log('%cAvailable commands:', 'text-decoration: underline');
+
+    const commands: string[] = [];
+    for (const cmd in Devtools) {
+      if (cmd !== '_internal_' && Devtools.hasOwnProperty(cmd)) {
+        commands.push(cmd);
+      }
+    }
+    commands.sort().forEach(function (cmd) {
+      window.console.log('\tDevtools.' + cmd + '()');
+    });
+
+    window.console.log('');
+  },
+
+  /**
+   * Disables/re-enables the editor autosave feature.
+   */
+  toggleEditorAutosave: (forceDisable: boolean): void => {
+    _settings.editorAutosave = forceDisable ? false : !_settings.editorAutosave;
+    _updateConfig();
+
+    window.console.log('%c\tEditor autosave ' + (_settings.editorAutosave ? 'enabled' : 'disabled'), 'font-style: italic');
+  },
+
+  /**
+   * Enables/disables logging for fired event listener events.
+   */
+  toggleEventLogging: function (forceEnable: boolean): void {
+    _settings.eventLogging = forceEnable ? true : !_settings.eventLogging;
+    _updateConfig();
+
+    window.console.log('%c\tEvent logging ' + (_settings.eventLogging ? 'enabled' : 'disabled'), 'font-style: italic');
+  },
+
+  /**
+   * Internal methods not meant to be called directly.
+   */
+  _internal_: {
+    enable: (): void => {
+      window.Devtools = Devtools;
+
+      window.console.log('%cDevtools for WoltLab Suite loaded', 'font-weight: bold');
+
+      if (window.sessionStorage) {
+        const settings = window.sessionStorage.getItem('__wsc_devtools_config');
+        try {
+          if (settings !== null) {
+            _settings = JSON.parse(settings);
+          }
+        } catch (e) {
+        }
+
+        if (!_settings.editorAutosave) Devtools.toggleEditorAutosave(true);
+        if (_settings.eventLogging) Devtools.toggleEventLogging(true);
+      }
+
+      window.console.log('Settings are saved per browser session, enter `Devtools.help()` to learn more.');
+      window.console.log('');
+    },
+
+    editorAutosave: (): boolean => _settings.editorAutosave,
+
+    eventLog: (identifier: string, action: string): void => {
+      if (_settings.eventLogging) {
+        window.console.log('[Devtools.EventLogging] Firing event: ' + action + ' @ ' + identifier);
+      }
+    },
+  },
+};
+
+export = Devtools;
index b8c93704cdf315b87569c1370252839ad409c806..4aef6c8337760559a6480aed7ba162a705f9fc35 100644 (file)
@@ -2,7 +2,7 @@
  * Dictionary implementation relying on an object or if supported on a Map to hold key => value data.
  *
  * If you're looking for a dictionary with object keys, please see `WoltLabSuite/Core/ObjectMap`.
- * 
+ *
  * This is a legacy implementation, that does not implement all methods of `Map`, furthermore it has
  * the side effect of converting all numeric keys to string values, treating 1 === "1".
  *
@@ -43,7 +43,7 @@ class Dictionary {
   /**
    * Retrieves a value by key, returns undefined if there is no match.
    */
-  get(key: number | string): any {
+  get(key: number | string): unknown {
     return this._dictionary.get(key.toString());
   }
 
@@ -52,6 +52,10 @@ class Dictionary {
    * value as first parameter and the key name second.
    */
   forEach(callback: (value: any, key: string) => void): void {
+    if (typeof callback !== 'function') {
+      throw new TypeError('forEach() expects a callback as first parameter.');
+    }
+
     this._dictionary.forEach(callback);
   }
 
@@ -81,7 +85,7 @@ class Dictionary {
    * All properties that are owned by the object will be added
    * as keys to the resulting Dictionary.
    */
-  static fromObject (object: object): Dictionary {
+  static fromObject(object: object): Dictionary {
     const result = new Dictionary();
 
     for (const key in object) {
@@ -92,7 +96,7 @@ class Dictionary {
 
     return result;
   }
-  
+
   get size(): number {
     return this._dictionary.size;
   }
diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Environment.ts b/wcfsetup/install/files/ts/WoltLabSuite/Core/Environment.ts
new file mode 100644 (file)
index 0000000..69cbe9b
--- /dev/null
@@ -0,0 +1,109 @@
+/**
+ * Provides basic details on the JavaScript environment.
+ *
+ * @author  Alexander Ebert
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module  Environment (alias)
+ * @module  WoltLabSuite/Core/Environment
+ */
+
+let _browser = 'other';
+let _editor = 'none';
+let _platform = 'desktop';
+let _touch = false;
+
+/**
+ * Determines environment variables.
+ */
+export function setup(): void {
+  if (typeof (window as any).chrome === 'object') {
+    // this detects Opera as well, we could check for window.opr if we need to
+    _browser = 'chrome';
+  } else {
+    const styles = window.getComputedStyle(document.documentElement);
+    for (let i = 0, length = styles.length; i < length; i++) {
+      const property = styles[i];
+
+      if (property.indexOf('-ms-') === 0) {
+        // it is tempting to use 'msie', but it wouldn't really represent 'Edge'
+        _browser = 'microsoft';
+      } else if (property.indexOf('-moz-') === 0) {
+        _browser = 'firefox';
+      } else if (_browser !== 'firefox' && property.indexOf('-webkit-') === 0) {
+        _browser = 'safari';
+      }
+    }
+  }
+
+  const ua = window.navigator.userAgent.toLowerCase();
+  if (ua.indexOf('crios') !== -1) {
+    _browser = 'chrome';
+    _platform = 'ios';
+  } else if (/(?:iphone|ipad|ipod)/.test(ua)) {
+    _browser = 'safari';
+    _platform = 'ios';
+  } else if (ua.indexOf('android') !== -1) {
+    _platform = 'android';
+  } else if (ua.indexOf('iemobile') !== -1) {
+    _browser = 'microsoft';
+    _platform = 'windows';
+  }
+
+  if (_platform === 'desktop' && (ua.indexOf('mobile') !== -1 || ua.indexOf('tablet') !== -1)) {
+    _platform = 'mobile';
+  }
+
+  _editor = 'redactor';
+  _touch = (('ontouchstart' in window) || (('msMaxTouchPoints' in window.navigator) && window.navigator.msMaxTouchPoints > 0) || (window as any).DocumentTouch && document instanceof (window as any).DocumentTouch);
+
+  // The iPad Pro 12.9" masquerades as a desktop browser.
+  if (window.navigator.platform === 'MacIntel' && window.navigator.maxTouchPoints > 1) {
+    _browser = 'safari';
+    _platform = 'ios';
+  }
+}
+
+/**
+ * Returns the lower-case browser identifier.
+ *
+ * Possible values:
+ *  - chrome: Chrome and Opera
+ *  - firefox
+ *  - microsoft: Internet Explorer and Microsoft Edge
+ *  - safari
+ */
+export function browser(): string {
+  return _browser;
+}
+
+/**
+ * Returns the available editor's name or an empty string.
+ */
+export function editor(): string {
+  return _editor;
+}
+
+/**
+ * Returns the browser platform.
+ *
+ * Possible values:
+ *  - desktop
+ *  - android
+ *  - ios: iPhone, iPad and iPod
+ *  - windows: Windows on phones/tablets
+ */
+export function platform(): string {
+  return _platform;
+}
+
+/**
+ * Returns true if browser is potentially used with a touchscreen.
+ *
+ * Warning: Detecting touch is unreliable and should be avoided at all cost.
+ *
+ * @deprecated  3.0 - exists for backward-compatibility only, will be removed in the future
+ */
+export function touch(): boolean {
+  return _touch;
+}
diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/FileUtil.ts b/wcfsetup/install/files/ts/WoltLabSuite/Core/FileUtil.ts
new file mode 100644 (file)
index 0000000..9504b30
--- /dev/null
@@ -0,0 +1,201 @@
+/**
+ * Provides helper functions for file handling.
+ *
+ * @author  Matthias Schmidt
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module  WoltLabSuite/Core/FileUtil
+ */
+
+import Dictionary from './Dictionary';
+import * as StringUtil from './StringUtil';
+
+
+const _fileExtensionIconMapping = Dictionary.fromObject({
+  // archive
+  zip: 'archive',
+  rar: 'archive',
+  tar: 'archive',
+  gz: 'archive',
+
+  // audio
+  mp3: 'audio',
+  ogg: 'audio',
+  wav: 'audio',
+
+  // code
+  php: 'code',
+  html: 'code',
+  htm: 'code',
+  tpl: 'code',
+  js: 'code',
+
+  // excel
+  xls: 'excel',
+  ods: 'excel',
+  xlsx: 'excel',
+
+  // image
+  gif: 'image',
+  jpg: 'image',
+  jpeg: 'image',
+  png: 'image',
+  bmp: 'image',
+  webp: 'image',
+
+  // video
+  avi: 'video',
+  wmv: 'video',
+  mov: 'video',
+  mp4: 'video',
+  mpg: 'video',
+  mpeg: 'video',
+  flv: 'video',
+
+  // pdf
+  pdf: 'pdf',
+
+  // powerpoint
+  ppt: 'powerpoint',
+  pptx: 'powerpoint',
+
+  // text
+  txt: 'text',
+
+  // word
+  doc: 'word',
+  docx: 'word',
+  odt: 'word',
+});
+
+const _mimeTypeExtensionMapping = Dictionary.fromObject({
+  // archive
+  'application/zip': 'zip',
+  'application/x-zip-compressed': 'zip',
+  'application/rar': 'rar',
+  'application/vnd.rar': 'rar',
+  'application/x-rar-compressed': 'rar',
+  'application/x-tar': 'tar',
+  'application/x-gzip': 'gz',
+  'application/gzip': 'gz',
+
+  // audio
+  'audio/mpeg': 'mp3',
+  'audio/mp3': 'mp3',
+  'audio/ogg': 'ogg',
+  'audio/x-wav': 'wav',
+
+  // code
+  'application/x-php': 'php',
+  'text/html': 'html',
+  'application/javascript': 'js',
+
+  // excel
+  'application/vnd.ms-excel': 'xls',
+  'application/vnd.oasis.opendocument.spreadsheet': 'ods',
+  'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'xlsx',
+
+  // image
+  'image/gif': 'gif',
+  'image/jpeg': 'jpg',
+  'image/png': 'png',
+  'image/x-ms-bmp': 'bmp',
+  'image/bmp': 'bmp',
+  'image/webp': 'webp',
+
+  // video
+  'video/x-msvideo': 'avi',
+  'video/x-ms-wmv': 'wmv',
+  'video/quicktime': 'mov',
+  'video/mp4': 'mp4',
+  'video/mpeg': 'mpg',
+  'video/x-flv': 'flv',
+
+  // pdf
+  'application/pdf': 'pdf',
+
+  // powerpoint
+  'application/vnd.ms-powerpoint': 'ppt',
+  'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'pptx',
+
+  // text
+  'text/plain': 'txt',
+
+  // word
+  'application/msword': 'doc',
+  'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'docx',
+  'application/vnd.oasis.opendocument.text': 'odt',
+});
+
+/**
+ * Formats the given filesize.
+ */
+export function formatFilesize(byte: number, precision: number): string {
+  if (precision === undefined) {
+    precision = 2;
+  }
+
+  let symbol = 'Byte';
+  if (byte >= 1000) {
+    byte /= 1000;
+    symbol = 'kB';
+  }
+  if (byte >= 1000) {
+    byte /= 1000;
+    symbol = 'MB';
+  }
+  if (byte >= 1000) {
+    byte /= 1000;
+    symbol = 'GB';
+  }
+  if (byte >= 1000) {
+    byte /= 1000;
+    symbol = 'TB';
+  }
+
+  return StringUtil.formatNumeric(byte, -precision) + ' ' + symbol;
+}
+
+/**
+ * Returns the icon name for given filename.
+ *
+ * Note: For any file icon name like `fa-file-word`, only `word`
+ * will be returned by this method.
+ */
+export function getIconNameByFilename(filename: string): string {
+  const lastDotPosition = filename.lastIndexOf('.');
+  if (lastDotPosition !== -1) {
+    const extension = filename.substr(lastDotPosition + 1);
+
+    if (_fileExtensionIconMapping.has(extension)) {
+      return _fileExtensionIconMapping.get(extension) as string;
+    }
+  }
+
+  return '';
+}
+
+/**
+ * Returns a known file extension including a leading dot or an empty string.
+ */
+export function getExtensionByMimeType(mimetype: string): string {
+  if (_mimeTypeExtensionMapping.has(mimetype)) {
+    return '.' + _mimeTypeExtensionMapping.get(mimetype);
+  }
+
+  return '';
+}
+
+
+/**
+ * Constructs a File object from a Blob
+ *
+ * @param       blob            the blob to convert
+ * @param       filename        the filename
+ * @returns     {File}          the File object
+ */
+export function blobToFile(blob: Blob, filename: string): File {
+  const ext = this.getExtensionByMimeType(blob.type);
+  return new File([blob], filename + ext, {type: blob.type});
+}
diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/I18n/Plural.ts b/wcfsetup/install/files/ts/WoltLabSuite/Core/I18n/Plural.ts
new file mode 100644 (file)
index 0000000..f838f30
--- /dev/null
@@ -0,0 +1,549 @@
+/**
+ * Generates plural phrases for the `plural` template plugin.
+ *
+ * @author  Matthias Schmidt, Marcel Werk
+ * @copyright  2001-2020 WoltLab GmbH
+ * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module  WoltLabSuite/Core/I18n/Plural
+ */
+
+import * as StringUtil from '../StringUtil';
+
+const PLURAL_FEW = 'few';
+const PLURAL_MANY = 'many';
+const PLURAL_ONE = 'one';
+const PLURAL_OTHER = 'other';
+const PLURAL_TWO = 'two';
+const PLURAL_ZERO = 'zero';
+
+const Plural = {
+  /**
+   * Returns the plural category for the given value.
+   */
+  getCategory: function (value: number, languageCode?: string): string {
+    if (!languageCode) {
+      languageCode = document.documentElement.lang;
+    }
+
+    // Fallback: handle unknown languages as English
+    if (typeof this[languageCode] !== 'function') {
+      languageCode = 'en';
+    }
+
+    const category = this[languageCode](value);
+    if (category) {
+      return category;
+    }
+
+    return PLURAL_OTHER;
+  },
+
+  /**
+   * Returns the value for a `plural` element used in the template.
+   *
+   * @see    wcf\system\template\plugin\PluralFunctionTemplatePlugin::execute()
+   */
+  getCategoryFromTemplateParameters: function (parameters: object): string {
+    if (!parameters['value']) {
+      throw new Error('Missing parameter value');
+    }
+    if (!parameters['other']) {
+      throw new Error('Missing parameter other');
+    }
+
+    let value = parameters['value'];
+    if (Array.isArray(value)) {
+      value = value.length;
+    }
+
+    // handle numeric attributes
+    for (const key in parameters) {
+      if (parameters.hasOwnProperty(key) && key.toString() === (~~key).toString() && key == value) {
+        return parameters[key];
+      }
+    }
+
+    let category = this.getCategory(value);
+    if (!parameters[category]) {
+      category = PLURAL_OTHER;
+    }
+
+    const string = parameters[category];
+    if (string.indexOf('#') !== -1) {
+      return string.replace('#', StringUtil.formatNumeric(value));
+    }
+
+    return string;
+  },
+
+  /**
+   * `f` is the fractional number as a whole number (1.234 yields 234)
+   */
+  getF: function (n: number): number {
+    const tmp = n.toString();
+    const pos = tmp.indexOf('.');
+    if (pos === -1) {
+      return 0;
+    }
+
+    return parseInt(tmp.substr(pos + 1), 10);
+  },
+
+  /**
+   * `v` represents the number of digits of the fractional part (1.234 yields 3)
+   */
+  getV: function (n: number): number {
+    return n.toString().replace(/^[^.]*\.?/, '').length;
+  },
+
+  // Afrikaans
+  af: function (n: number): string | undefined {
+    if (n == 1) return PLURAL_ONE;
+  },
+
+  // Amharic
+  am: function (n: number): string | undefined {
+    const i = Math.floor(Math.abs(n));
+    if (n == 1 || i === 0) return PLURAL_ONE;
+  },
+
+  // Arabic
+  ar: function (n: number): string | undefined {
+    if (n == 0) return PLURAL_ZERO;
+    if (n == 1) return PLURAL_ONE;
+    if (n == 2) return PLURAL_TWO;
+
+    const mod100 = n % 100;
+    if (mod100 >= 3 && mod100 <= 10) return PLURAL_FEW;
+    if (mod100 >= 11 && mod100 <= 99) return PLURAL_MANY;
+  },
+
+  // Assamese
+  as: function (n: number): string | undefined {
+    const i = Math.floor(Math.abs(n));
+    if (n == 1 || i === 0) return PLURAL_ONE;
+  },
+
+  // Azerbaijani
+  az: function (n: number): string | undefined {
+    if (n == 1) return PLURAL_ONE;
+  },
+
+  // Belarusian
+  be: function (n: number): string | undefined {
+    const mod10 = n % 10;
+    const mod100 = n % 100;
+
+    if (mod10 == 1 && mod100 != 11) return PLURAL_ONE;
+    if (mod10 >= 2 && mod10 <= 4 && !(mod100 >= 12 && mod100 <= 14)) return PLURAL_FEW;
+    if (mod10 == 0 || (mod10 >= 5 && mod10 <= 9) || (mod100 >= 11 && mod100 <= 14)) return PLURAL_MANY;
+  },
+
+  // Bulgarian
+  bg: function (n: number): string | undefined {
+    if (n == 1) return PLURAL_ONE;
+  },
+
+  // Bengali
+  bn: function (n: number): string | undefined {
+    const i = Math.floor(Math.abs(n));
+    if (n == 1 || i === 0) return PLURAL_ONE;
+  },
+
+  // Tibetan
+  bo: function (n: number) {
+  },
+
+  // Bosnian
+  bs: function (n: number): string | undefined {
+    const v = this.getV(n);
+    const f = this.getF(n);
+    const mod10 = n % 10;
+    const mod100 = n % 100;
+    const fMod10 = f % 10;
+    const fMod100 = f % 100;
+
+    if ((v == 0 && mod10 == 1 && mod100 != 11) || (fMod10 == 1 && fMod100 != 11)) return PLURAL_ONE;
+    if ((v == 0 && mod10 >= 2 && mod10 <= 4 && mod100 >= 12 && mod100 <= 14)
+      || (fMod10 >= 2 && fMod10 <= 4 && fMod100 >= 12 && fMod100 <= 14)) return PLURAL_FEW;
+  },
+
+  // Czech
+  cs: function (n: number): string | undefined {
+    const v = this.getV(n);
+
+    if (n == 1 && v === 0) return PLURAL_ONE;
+    if (n >= 2 && n <= 4 && v === 0) return PLURAL_FEW;
+    if (v === 0) return PLURAL_MANY;
+  },
+
+  // Welsh
+  cy: function (n: number): string | undefined {
+    if (n == 0) return PLURAL_ZERO;
+    if (n == 1) return PLURAL_ONE;
+    if (n == 2) return PLURAL_TWO;
+    if (n == 3) return PLURAL_FEW;
+    if (n == 6) return PLURAL_MANY;
+  },
+
+  // Danish
+  da: function (n: number): string | undefined {
+    if (n > 0 && n < 2) return PLURAL_ONE;
+  },
+
+  // Greek
+  el: function (n: number): string | undefined {
+    if (n == 1) return PLURAL_ONE;
+  },
+
+  // Catalan (ca)
+  // German (de)
+  // English (en)
+  // Estonian (et)
+  // Finnish (fi)
+  // Italian (it)
+  // Dutch (nl)
+  // Swedish (sv)
+  // Swahili (sw)
+  // Urdu (ur)
+  en: function (n: number): string | undefined {
+    if (n == 1 && this.getV(n) === 0) return PLURAL_ONE;
+  },
+
+  // Spanish
+  es: function (n: number): string | undefined {
+    if (n == 1) return PLURAL_ONE;
+  },
+
+  // Basque
+  eu: function (n: number): string | undefined {
+    if (n == 1) return PLURAL_ONE;
+  },
+
+  // Persian
+  fa: function (n: number): string | undefined {
+    if (n >= 0 && n <= 1) return PLURAL_ONE;
+  },
+
+  // French
+  fr: function (n: number): string | undefined {
+    if (n >= 0 && n < 2) return PLURAL_ONE;
+  },
+
+  // Irish
+  ga: function (n: number): string | undefined {
+    if (n == 1) return PLURAL_ONE;
+    if (n == 2) return PLURAL_TWO;
+    if (n == 3 || n == 4 || n == 5 || n == 6) return PLURAL_FEW;
+    if (n == 7 || n == 8 || n == 9 || n == 10) return PLURAL_MANY;
+  },
+
+  // Gujarati
+  gu: function (n: number): string | undefined {
+    if (n >= 0 && n <= 1) return PLURAL_ONE;
+  },
+
+  // Hebrew
+  he: function (n: number): string | undefined {
+    const v = this.getV(n);
+
+    if (n == 1 && v === 0) return PLURAL_ONE;
+    if (n == 2 && v === 0) return PLURAL_TWO;
+    if (n > 10 && v === 0 && n % 10 == 0) return PLURAL_MANY;
+  },
+
+  // Hindi
+  hi: function (n: number): string | undefined {
+    if (n >= 0 && n <= 1) return PLURAL_ONE;
+  },
+
+  // Croatian
+  hr: function (n: number): string | undefined {
+    // same as Bosnian
+    return this.bs(n);
+  },
+
+  // Hungarian
+  hu: function (n: number): string | undefined {
+    if (n == 1) return PLURAL_ONE;
+  },
+
+  // Armenian
+  hy: function (n: number): string | undefined {
+    if (n >= 0 && n < 2) return PLURAL_ONE;
+  },
+
+  // Indonesian
+  id: function (n: number) {
+  },
+
+  // Icelandic
+  is: function (n: number): string | undefined {
+    const f = this.getF(n);
+
+    if (f === 0 && n % 10 === 1 && !(n % 100 === 11) || !(f === 0)) return PLURAL_ONE;
+  },
+
+  // Japanese
+  ja: function (n: number) {
+  },
+
+  // Javanese
+  jv: function (n: number) {
+  },
+
+  // Georgian
+  ka: function (n: number): string | undefined {
+    if (n == 1) return PLURAL_ONE;
+  },
+
+  // Kazakh
+  kk: function (n: number): string | undefined {
+    if (n == 1) return PLURAL_ONE;
+  },
+
+  // Khmer
+  km: function (n: number) {
+  },
+
+  // Kannada
+  kn: function (n: number): string | undefined {
+    if (n >= 0 && n <= 1) return PLURAL_ONE;
+  },
+
+  // Korean
+  ko: function (n: number) {
+  },
+
+  // Kurdish
+  ku: function (n: number): string | undefined {
+    if (n == 1) return PLURAL_ONE;
+  },
+
+  // Kyrgyz
+  ky: function (n: number): string | undefined {
+    if (n == 1) return PLURAL_ONE;
+  },
+
+  // Luxembourgish
+  lb: function (n: number): string | undefined {
+    if (n == 1) return PLURAL_ONE;
+  },
+
+  // Lao
+  lo: function (n: number) {
+  },
+
+  // Lithuanian
+  lt: function (n: number): string | undefined {
+    const mod10 = n % 10;
+    const mod100 = n % 100;
+
+    if (mod10 == 1 && !(mod100 >= 11 && mod100 <= 19)) return PLURAL_ONE;
+    if (mod10 >= 2 && mod10 <= 9 && !(mod100 >= 11 && mod100 <= 19)) return PLURAL_FEW;
+    if (this.getF(n) != 0) return PLURAL_MANY;
+  },
+
+  // Latvian
+  lv: function (n: number): string | undefined {
+    const mod10 = n % 10;
+    const mod100 = n % 100;
+    const v = this.getV(n);
+    const f = this.getF(n);
+    const fMod10 = f % 10;
+    const fMod100 = f % 100;
+
+    if (mod10 == 0 || (mod100 >= 11 && mod100 <= 19) || (v == 2 && fMod100 >= 11 && fMod100 <= 19)) return PLURAL_ZERO;
+    if ((mod10 == 1 && mod100 != 11) || (v == 2 && fMod10 == 1 && fMod100 != 11) || (v != 2 && fMod10 == 1)) return PLURAL_ONE;
+  },
+
+  // Macedonian
+  mk: function (n: number): string | undefined {
+    return this.bs(n);
+  },
+
+  // Malayalam
+  ml: function (n: number): string | undefined {
+    if (n == 1) return PLURAL_ONE;
+  },
+
+  // Mongolian 
+  mn: function (n: number): string | undefined {
+    if (n == 1) return PLURAL_ONE;
+  },
+
+  // Marathi 
+  mr: function (n: number): string | undefined {
+    if (n == 1) return PLURAL_ONE;
+  },
+
+  // Malay 
+  ms: function (n: number) {
+  },
+
+  // Maltese 
+  mt: function (n: number): string | undefined {
+    const mod100 = n % 100;
+
+    if (n == 1) return PLURAL_ONE;
+    if (n == 0 || (mod100 >= 2 && mod100 <= 10)) return PLURAL_FEW;
+    if (mod100 >= 11 && mod100 <= 19) return PLURAL_MANY;
+  },
+
+  // Burmese
+  my: function (n: number) {
+  },
+
+  // Norwegian
+  no: function (n: number): string | undefined {
+    if (n == 1) return PLURAL_ONE;
+  },
+
+  // Nepali
+  ne: function (n: number): string | undefined {
+    if (n == 1) return PLURAL_ONE;
+  },
+
+  // Odia
+  or: function (n: number): string | undefined {
+    if (n == 1) return PLURAL_ONE;
+  },
+
+  // Punjabi
+  pa: function (n: number): string | undefined {
+    if (n == 1 || n == 0) return PLURAL_ONE;
+  },
+
+  // Polish
+  pl: function (n: number): string | undefined {
+    const v = this.getV(n);
+    const mod10 = n % 10;
+    const mod100 = n % 100;
+
+    if (n == 1 && v == 0) return PLURAL_ONE;
+    if (v == 0 && mod10 >= 2 && mod10 <= 4 && !(mod100 >= 12 && mod100 <= 14)) return PLURAL_FEW;
+    if (v == 0 && ((n != 1 && mod10 >= 0 && mod10 <= 1) || (mod10 >= 5 && mod10 <= 9) || (mod100 >= 12 && mod100 <= 14))) return PLURAL_MANY;
+  },
+
+  // Pashto
+  ps: function (n: number): string | undefined {
+    if (n == 1) return PLURAL_ONE;
+  },
+
+  // Portuguese
+  pt: function (n: number): string | undefined {
+    if (n >= 0 && n < 2) return PLURAL_ONE;
+  },
+
+  // Romanian
+  ro: function (n: number): string | undefined {
+    const v = this.getV(n);
+    const mod100 = n % 100;
+
+    if (n == 1 && v === 0) return PLURAL_ONE;
+    if (v != 0 || n == 0 || (mod100 >= 2 && mod100 <= 19)) return PLURAL_FEW;
+  },
+
+  // Russian
+  ru: function (n: number): string | undefined {
+    const mod10 = n % 10;
+    const mod100 = n % 100;
+
+    if (this.getV(n) == 0) {
+      if (mod10 == 1 && mod100 != 11) return PLURAL_ONE;
+      if (mod10 >= 2 && mod10 <= 4 && !(mod100 >= 12 && mod100 <= 14)) return PLURAL_FEW;
+      if (mod10 == 0 || (mod10 >= 5 && mod10 <= 9) || (mod100 >= 11 && mod100 <= 14)) return PLURAL_MANY;
+    }
+  },
+
+  // Sindhi
+  sd: function (n: number): string | undefined {
+    if (n == 1) return PLURAL_ONE;
+  },
+
+  // Sinhala
+  si: function (n: number): string | undefined {
+    if (n == 0 || n == 1 || (Math.floor(n) == 0 && this.getF(n) == 1)) return PLURAL_ONE;
+  },
+
+  // Slovak
+  sk: function (n: number): string | undefined {
+    // same as Czech
+    return this.cs(n);
+  },
+
+  // Slovenian
+  sl: function (n: number): string | undefined {
+    const v = this.getV(n);
+    const mod100 = n % 100;
+
+    if (v == 0 && mod100 == 1) return PLURAL_ONE;
+    if (v == 0 && mod100 == 2) return PLURAL_TWO;
+    if ((v == 0 && (mod100 == 3 || mod100 == 4)) || v != 0) return PLURAL_FEW;
+  },
+
+  // Albanian
+  sq: function (n: number): string | undefined {
+    if (n == 1) return PLURAL_ONE;
+  },
+
+  // Serbian
+  sr: function (n: number): string | undefined {
+    // same as Bosnian
+    return this.bs(n);
+  },
+
+  // Tamil
+  ta: function (n: number): string | undefined {
+    if (n == 1) return PLURAL_ONE;
+  },
+
+  // Telugu
+  te: function (n: number): string | undefined {
+    if (n == 1) return PLURAL_ONE;
+  },
+
+  // Tajik
+  tg: function (n: number) {
+  },
+
+  // Thai
+  th: function (n: number) {
+  },
+
+  // Turkmen
+  tk: function (n: number): string | undefined {
+    if (n == 1) return PLURAL_ONE;
+  },
+
+  // Turkish
+  tr: function (n: number): string | undefined {
+    if (n == 1) return PLURAL_ONE;
+  },
+
+  // Uyghur
+  ug: function (n: number): string | undefined {
+    if (n == 1) return PLURAL_ONE;
+  },
+
+  // Ukrainian
+  uk: function (n: number): string | undefined {
+    // same as Russian
+    return this.ru(n);
+  },
+
+  // Uzbek
+  uz: function (n: number): string | undefined {
+    if (n == 1) return PLURAL_ONE;
+  },
+
+  // Vietnamese
+  vi: function (n: number) {
+  },
+
+  // Chinese
+  zh: function (n: number) {
+  },
+};
+
+export = Plural
diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Language.ts b/wcfsetup/install/files/ts/WoltLabSuite/Core/Language.ts
new file mode 100644 (file)
index 0000000..474750e
--- /dev/null
@@ -0,0 +1,64 @@
+/**
+ * Manages language items.
+ *
+ * @author  Tim Duesterhus
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module  Language (alias)
+ * @module  WoltLabSuite/Core/Language
+ */
+import Dictionary from './Dictionary';
+import Template from './Template';
+
+const _languageItems = new Dictionary();
+
+/**
+ * Adds all the language items in the given object to the store.
+ */
+export function addObject(object: object): void {
+  _languageItems.merge(Dictionary.fromObject(object));
+}
+
+/**
+ * Adds a single language item to the store.
+ */
+export function add(key: string, value: string): void {
+  _languageItems.set(key, value);
+}
+
+/**
+ * Fetches the language item specified by the given key.
+ * If the language item is a string it will be evaluated as
+ * WoltLabSuite/Core/Template with the given parameters.
+ *
+ * @param  {string}  key    Language item to return.
+ * @param  {Object=}  parameters  Parameters to provide to WoltLabSuite/Core/Template.
+ * @return  {string}
+ */
+export function get(key: string, parameters?: object): string {
+  let value = _languageItems.get(key);
+  if (value === undefined) {
+    return key;
+  }
+
+  // fetch Template, as it cannot be provided because of a circular dependency
+  if (Template === undefined) { //@ts-ignore
+    Template = require('./Template');
+  }
+
+  if (typeof value === 'string') {
+    // lazily convert to WCF.Template
+    try {
+      _languageItems.set(key, new Template(value));
+    } catch (e) {
+      _languageItems.set(key, new Template('{literal}' + value.replace(/{\/literal}/g, '{/literal}{ldelim}/literal}{literal}') + '{/literal}'));
+    }
+    value = _languageItems.get(key);
+  }
+
+  if (value instanceof Template) {
+    value = (value as Template).fetch(parameters || {});
+  }
+
+  return value as string;
+}
diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/List.ts b/wcfsetup/install/files/ts/WoltLabSuite/Core/List.ts
new file mode 100644 (file)
index 0000000..4ac69f7
--- /dev/null
@@ -0,0 +1,56 @@
+/**
+ * List implementation relying on an array or if supported on a Set to hold values.
+ *
+ * @author  Alexander Ebert
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module  List (alias)
+ * @module  WoltLabSuite/Core/List
+ */
+/**
+ * @constructor
+ */
+class List {
+  private _set = new Set<any>();
+
+  /**
+   * Appends an element to the list, silently rejects adding an already existing value.
+   */
+  add(value: any): void {
+    this._set.add(value);
+  }
+
+  /**
+   * Removes all elements from the list.
+   */
+  clear(): void {
+    this._set.clear();
+  }
+
+  /**
+   * Removes an element from the list, returns true if the element was in the list.
+   */
+  delete(value: any): boolean {
+    return this._set.delete(value);
+  }
+
+  /**
+   * Invokes the `callback` for each element in the list.
+   */
+  forEach(callback: (value: any) => void): void {
+    this._set.forEach(callback);
+  }
+
+  /**
+   * Returns true if the list contains the element.
+   */
+  has(value: any): boolean {
+    return this._set.has(value);
+  }
+
+  get size(): number {
+    return this._set.size;
+  }
+}
+
+export = List;
diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/NumberUtil.ts b/wcfsetup/install/files/ts/WoltLabSuite/Core/NumberUtil.ts
new file mode 100644 (file)
index 0000000..d73ac0c
--- /dev/null
@@ -0,0 +1,36 @@
+/**
+ * Provides helper functions for Number handling.
+ *
+ * @author  Tim Duesterhus
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module  WoltLabSuite/Core/NumberUtil
+ */
+
+/**
+ * Decimal adjustment of a number.
+ *
+ * @see  https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
+ */
+export function round(value: number, exp: number): number {
+  // If the exp is undefined or zero...
+  if (typeof exp === 'undefined' || +exp === 0) {
+    return Math.round(value);
+  }
+  value = +value;
+  exp = +exp;
+
+  // If the value is not a number or the exp is not an integer...
+  if (isNaN(value) || !(typeof (exp as any) === 'number' && exp % 1 === 0)) {
+    return NaN;
+  }
+
+  // Shift
+  let tmp = value.toString().split('e');
+  value = Math.round(+(tmp[0] + 'e' + (tmp[1] ? (+tmp[1] - exp) : -exp)));
+
+  // Shift back
+  tmp = value.toString().split('e');
+  return +(tmp[0] + 'e' + (tmp[1] ? (+tmp[1] + exp) : exp));
+}
+       
diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/ObjectMap.ts b/wcfsetup/install/files/ts/WoltLabSuite/Core/ObjectMap.ts
new file mode 100644 (file)
index 0000000..755ebe7
--- /dev/null
@@ -0,0 +1,55 @@
+/**
+ * Simple `object` to `object` map using a WeakMap.
+ *
+ * If you're looking for a dictionary with string keys, please see `WoltLabSuite/Core/Dictionary`.
+ *
+ * @author  Alexander Ebert
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module  ObjectMap (alias)
+ * @module  WoltLabSuite/Core/ObjectMap
+ */
+
+class ObjectMap {
+  private _map = new WeakMap<object, object>();
+
+  /**
+   * Sets a new key with given value, will overwrite an existing key.
+   */
+  set(key: object, value: object): void {
+    if (typeof key !== 'object' || key === null) {
+      throw new TypeError('Only objects can be used as key');
+    }
+
+    if (typeof value !== 'object' || value === null) {
+      throw new TypeError('Only objects can be used as value');
+    }
+
+    this._map.set(key, value);
+  }
+
+  /**
+   * Removes a key from the map.
+   */
+  delete(key: object): void {
+    this._map.delete(key);
+
+  }
+
+  /**
+   * Returns true if dictionary contains a value for given key.
+   */
+  has(key: object): boolean {
+    return this._map.has(key);
+  }
+
+
+  /**
+   * Retrieves a value by key, returns undefined if there is no match.
+   */
+  get(key: object): object | undefined {
+    return this._map.get(key);
+  }
+}
+
+export = ObjectMap
diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Permission.ts b/wcfsetup/install/files/ts/WoltLabSuite/Core/Permission.ts
new file mode 100644 (file)
index 0000000..09a83f8
--- /dev/null
@@ -0,0 +1,52 @@
+/**
+ * Manages user permissions.
+ *
+ * @author  Matthias Schmidt
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module  Permission (alias)
+ * @module  WoltLabSuite/Core/Permission
+ */
+
+const _permissions = new Map<string, boolean>();
+
+/**
+ * Adds a single permission to the store.
+ */
+export function add(permission: string, value: boolean): void {
+  if (typeof (value as any) !== 'boolean') {
+    throw new TypeError('The permission value has to be boolean.');
+  }
+
+  _permissions.set(permission, value);
+}
+
+/**
+ * Adds all the permissions in the given object to the store.
+ */
+export function addObject(object: PermissionObject): void {
+  for (const key in object) {
+    if (object.hasOwnProperty(key)) {
+
+      this.add(key, object[key]);
+    }
+  }
+}
+
+
+/**
+ * Returns the value of a permission.
+ *
+ * If the permission is unknown, false is returned.
+ */
+export function get(permission: string): boolean {
+  if (_permissions.has(permission)) {
+    return _permissions.get(permission)!;
+  }
+
+  return false;
+}
+
+interface PermissionObject {
+  [key: string]: boolean;
+}
diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/StringUtil.ts b/wcfsetup/install/files/ts/WoltLabSuite/Core/StringUtil.ts
new file mode 100644 (file)
index 0000000..ed67d0f
--- /dev/null
@@ -0,0 +1,113 @@
+/**
+ * Provides helper functions for String handling.
+ *
+ * @author  Tim Duesterhus, Joshua Ruesweg
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module  StringUtil (alias)
+ * @module  WoltLabSuite/Core/StringUtil
+ */
+import * as Language from './Language';
+import * as NumberUtil from './NumberUtil';
+
+/**
+ * Adds thousands separators to a given number.
+ *
+ * @see    http://stackoverflow.com/a/6502556/782822
+ */
+export function addThousandsSeparator(number: number): string {
+  // Fetch Language, as it cannot be provided because of a circular dependency
+  if (Language === undefined) { //@ts-ignore
+    Language = require('./Language');
+  }
+
+  return String(number).replace(/(^-?\d{1,3}|\d{3})(?=(?:\d{3})+(?:$|\.))/g, '$1' + Language.get('wcf.global.thousandsSeparator'));
+}
+
+/**
+ * Escapes special HTML-characters within a string
+ */
+export function escapeHTML(string: string): string {
+  return String(string).replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+}
+
+/**
+ * Escapes a String to work with RegExp.
+ *
+ * @see    https://github.com/sstephenson/prototype/blob/master/src/prototype/lang/regexp.js#L25
+ */
+export function escapeRegExp(string: string): string {
+  return String(string).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
+}
+
+/**
+ * Rounds number to given count of floating point digits, localizes decimal-point and inserts thousands separators.
+ */
+export function formatNumeric(number: number, decimalPlaces?: number): string {
+  // Fetch Language, as it cannot be provided because of a circular dependency
+  if (Language === undefined) { //@ts-ignore
+    Language = require('./Language');
+  }
+
+  let tmp = NumberUtil.round(number, decimalPlaces || -2).toString();
+  const numberParts = tmp.split('.');
+
+  tmp = this.addThousandsSeparator(+numberParts[0]);
+  if (numberParts.length > 1) tmp += Language.get('wcf.global.decimalPoint') + numberParts[1];
+
+  tmp = tmp.replace('-', '\u2212');
+
+  return tmp;
+}
+
+/**
+ * Makes a string's first character lowercase.
+ */
+export function lcfirst(string: string): string {
+  return String(string).substring(0, 1).toLowerCase() + string.substring(1);
+}
+
+/**
+ * Makes a string's first character uppercase.
+ */
+export function ucfirst(string: string): string {
+  return String(string).substring(0, 1).toUpperCase() + string.substring(1);
+}
+
+/**
+ * Unescapes special HTML-characters within a string.
+ */
+export function unescapeHTML(string: string): string {
+  return String(string).replace(/&amp;/g, '&').replace(/&quot;/g, '"').replace(/&lt;/g, '<').replace(/&gt;/g, '>');
+}
+
+/**
+ * Shortens numbers larger than 1000 by using unit suffixes.
+ */
+export function shortUnit(number: number): string {
+  let unitSuffix = '';
+
+  if (number >= 1000000) {
+    number /= 1000000;
+
+    if (number > 10) {
+      number = Math.floor(number);
+    } else {
+      number = NumberUtil.round(number, -1);
+    }
+
+    unitSuffix = 'M';
+  } else if (number >= 1000) {
+    number /= 1000;
+
+    if (number > 10) {
+      number = Math.floor(number);
+    } else {
+      number = NumberUtil.round(number, -1);
+    }
+
+    unitSuffix = 'k';
+  }
+
+  return this.formatNumeric(number) + unitSuffix;
+}
diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Template.grammar.d.ts b/wcfsetup/install/files/ts/WoltLabSuite/Core/Template.grammar.d.ts
new file mode 100644 (file)
index 0000000..c855d67
--- /dev/null
@@ -0,0 +1 @@
+export function parse(input: string): unknown;
diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Template.ts b/wcfsetup/install/files/ts/WoltLabSuite/Core/Template.ts
new file mode 100644 (file)
index 0000000..4131a88
--- /dev/null
@@ -0,0 +1,80 @@
+/**
+ * WoltLabSuite/Core/Template provides a template scripting compiler similar
+ * to the PHP one of WoltLab Suite Core. It supports a limited
+ * set of useful commands and compiles templates down to a pure
+ * JavaScript Function.
+ *
+ * @author  Tim Duesterhus
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module  WoltLabSuite/Core/Template
+ */
+import * as parser from './Template.grammar';
+import * as StringUtil from './StringUtil';
+import * as Language from './Language';
+import * as I18nPlural from './I18n/Plural';
+
+// @todo: still required?
+// work around bug in AMD module generation of Jison
+/*function Parser() {
+  this.yy = {};
+}
+
+Parser.prototype = parser;
+parser.Parser = Parser;
+parser = new Parser();*/
+
+/**
+ * Compiles the given template.
+ *
+ * @param  {string}  template  Template to compile.
+ * @constructor
+ */
+class Template {
+  constructor(template: string) {
+    // Fetch Language/StringUtil, as it cannot be provided because of a circular dependency
+    if (Language === undefined) { //@ts-ignore
+      Language = require('./Language');
+    }
+    if (StringUtil === undefined) { //@ts-ignore
+      StringUtil = require('./StringUtil');
+    }
+
+    try {
+      template = parser.parse(template) as string;
+      template = 'var tmp = {};\n'
+        + 'for (var key in v) tmp[key] = v[key];\n'
+        + 'v = tmp;\n'
+        + 'v.__wcf = window.WCF; v.__window = window;\n'
+        + 'return ' + template;
+
+      this.fetch = new Function('StringUtil', 'Language', 'I18nPlural', 'v', template).bind(undefined, StringUtil, Language, I18nPlural);
+    } catch (e) {
+      console.debug(e.message);
+      throw e;
+    }
+  }
+
+  /**
+   * Evaluates the Template using the given parameters.
+   *
+   * @param  {object}  v  Parameters to pass to the template.
+   */
+  fetch(v: object): string {
+    // this will be replaced in the init function
+    throw new Error('This Template is not initialized.');
+  }
+}
+
+Object.defineProperty(Template, 'callbacks', {
+  enumerable: false,
+  configurable: false,
+  get: function () {
+    throw new Error('WCF.Template.callbacks is no longer supported');
+  },
+  set: function (value) {
+    throw new Error('WCF.Template.callbacks is no longer supported');
+  },
+});
+
+export = Template;
diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/User.ts b/wcfsetup/install/files/ts/WoltLabSuite/Core/User.ts
new file mode 100644 (file)
index 0000000..29c3983
--- /dev/null
@@ -0,0 +1,40 @@
+/**
+ * Provides data of the active user.
+ *
+ * @author  Matthias Schmidt
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module  User (alias)
+ * @module  WoltLabSuite/Core/User
+ */
+
+let _link: string | undefined;
+
+export = {
+  /**
+   * Returns the link to the active user's profile or an empty string
+   * if the active user is a guest.
+   */
+  getLink: (): string => _link || '',
+
+  /**
+   * Initializes the user object.
+   */
+  init: (userId: number, username: string, link: string): void => {
+    if (_link !== undefined) {
+      throw new Error('User has already been initialized.');
+    }
+
+    // define non-writeable properties for userId and username
+    Object.defineProperty(this, 'userId', {
+      value: userId,
+      writable: false,
+    });
+    Object.defineProperty(this, 'username', {
+      value: username,
+      writable: false,
+    });
+
+    _link = link || '';
+  },
+}