Merge branch '6.0'
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / js / WoltLabSuite / Core / Core.js
CommitLineData
c9c0b67c
AE
1/**
2 * Provides the basic core functionality.
3 *
4 * @author Alexander Ebert
5 * @copyright 2001-2019 WoltLab GmbH
6 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
c9c0b67c
AE
7 */
8define(["require", "exports"], function (require, exports) {
9 "use strict";
10 Object.defineProperty(exports, "__esModule", { value: true });
054537bf
AE
11 exports.clone = clone;
12 exports.convertLegacyUrl = convertLegacyUrl;
13 exports.extend = extend;
14 exports.inherit = inherit;
15 exports.isPlainObject = isPlainObject;
16 exports.getType = getType;
17 exports.getUuid = getUuid;
18 exports.serialize = serialize;
19 exports.triggerEvent = triggerEvent;
20 exports.getStoragePrefix = getStoragePrefix;
21 exports.stringToBool = stringToBool;
22 exports.debounce = debounce;
23 exports.enableLegacyInheritance = enableLegacyInheritance;
24 exports.getXsrfToken = getXsrfToken;
c9c0b67c 25 const _clone = function (variable) {
6ab9b916 26 if (typeof variable === "object" && (Array.isArray(variable) || isPlainObject(variable))) {
c9c0b67c
AE
27 return _cloneObject(variable);
28 }
29 return variable;
30 };
31 const _cloneObject = function (obj) {
32 if (!obj) {
33 return null;
34 }
35 if (Array.isArray(obj)) {
36 return obj.slice();
37 }
38 const newObj = {};
6ab9b916 39 Object.keys(obj).forEach((key) => (newObj[key] = _clone(obj[key])));
c9c0b67c
AE
40 return newObj;
41 };
6ab9b916 42 const _prefix = "wsc" + window.WCF_PATH.hashCode() + "-";
c9c0b67c
AE
43 /**
44 * Deep clones an object.
45 */
46 function clone(obj) {
47 return _clone(obj);
48 }
c9c0b67c
AE
49 /**
50 * Converts WCF 2.0-style URLs into the default URL layout.
51 */
52 function convertLegacyUrl(url) {
53 return url.replace(/^index\.php\/(.*?)\/\?/, (match, controller) => {
54 const parts = controller.split(/([A-Z][a-z0-9]+)/);
6ab9b916 55 controller = "";
c9c0b67c
AE
56 for (let i = 0, length = parts.length; i < length; i++) {
57 const part = parts[i].trim();
58 if (part.length) {
665fa171 59 if (controller.length) {
6ab9b916 60 controller += "-";
665fa171 61 }
c9c0b67c
AE
62 controller += part.toLowerCase();
63 }
64 }
65 return `index.php?${controller}/&`;
66 });
67 }
c9c0b67c
AE
68 /**
69 * Merges objects with the first argument.
70 *
71 * @param {object} out destination object
72 * @param {...object} args variable number of objects to be merged into the destination object
73 * @return {object} destination object with all provided objects merged into
74 */
75 function extend(out, ...args) {
76 out = out || {};
77 const newObj = clone(out);
592ea62f 78 for (let i = 0, length = args.length; i < length; i++) {
c9c0b67c 79 const obj = args[i];
665fa171 80 if (!obj) {
c9c0b67c 81 continue;
665fa171
AE
82 }
83 Object.keys(obj).forEach((key) => {
84 if (!Array.isArray(obj[key]) && typeof obj[key] === "object") {
85 if (isPlainObject(obj[key])) {
86 // object literals have the prototype of Object which in return has no parent prototype
87 newObj[key] = extend(out[key], obj[key]);
c9c0b67c
AE
88 }
89 else {
90 newObj[key] = obj[key];
91 }
92 }
665fa171
AE
93 else {
94 newObj[key] = obj[key];
95 }
96 });
c9c0b67c
AE
97 }
98 return newObj;
99 }
c9c0b67c
AE
100 /**
101 * Inherits the prototype methods from one constructor to another
102 * constructor.
103 *
104 * Usage:
105 *
106 * function MyDerivedClass() {}
107 * Core.inherit(MyDerivedClass, TheAwesomeBaseClass, {
108 * // regular prototype for `MyDerivedClass`
109 *
110 * overwrittenMethodFromBaseClass: function(foo, bar) {
111 * // do stuff
112 *
113 * // invoke parent
114 * MyDerivedClass._super.prototype.overwrittenMethodFromBaseClass.call(this, foo, bar);
115 * }
116 * });
117 *
118 * @see https://github.com/nodejs/node/blob/7d14dd9b5e78faabb95d454a79faa513d0bbc2a5/lib/util.js#L697-L735
e2fdcc2c 119 * @deprecated 5.4 Use the native `class` and `extends` keywords instead.
c9c0b67c
AE
120 */
121 function inherit(constructor, superConstructor, propertiesObject) {
122 if (constructor === undefined || constructor === null) {
6ab9b916 123 throw new TypeError("The constructor must not be undefined or null.");
c9c0b67c
AE
124 }
125 if (superConstructor === undefined || superConstructor === null) {
6ab9b916 126 throw new TypeError("The super constructor must not be undefined or null.");
c9c0b67c
AE
127 }
128 if (superConstructor.prototype === undefined) {
6ab9b916 129 throw new TypeError("The super constructor must have a prototype.");
c9c0b67c
AE
130 }
131 constructor._super = superConstructor;
132 constructor.prototype = extend(Object.create(superConstructor.prototype, {
133 constructor: {
134 configurable: true,
135 enumerable: false,
136 value: constructor,
137 writable: true,
138 },
139 }), propertiesObject || {});
140 }
c9c0b67c
AE
141 /**
142 * Returns true if `obj` is an object literal.
143 */
144 function isPlainObject(obj) {
665fa171 145 if (typeof obj !== "object" || obj === null) {
c9c0b67c
AE
146 return false;
147 }
6ab9b916 148 return Object.getPrototypeOf(obj) === Object.prototype;
c9c0b67c 149 }
c9c0b67c
AE
150 /**
151 * Returns the object's class name.
152 */
153 function getType(obj) {
6ab9b916 154 return Object.prototype.toString.call(obj).replace(/^\[object (.+)]$/, "$1");
c9c0b67c 155 }
c9c0b67c
AE
156 /**
157 * Returns a RFC4122 version 4 compilant UUID.
158 *
159 * @see http://stackoverflow.com/a/2117523
160 */
161 function getUuid() {
6ab9b916
TD
162 return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
163 const r = (Math.random() * 16) | 0, v = c == "x" ? r : (r & 0x3) | 0x8;
c9c0b67c
AE
164 return v.toString(16);
165 });
166 }
c9c0b67c
AE
167 /**
168 * Recursively serializes an object into an encoded URI parameter string.
169 */
170 function serialize(obj, prefix) {
72700514
AE
171 if (obj === null) {
172 return "";
173 }
3e862cb8 174 const parameters = [];
665fa171
AE
175 Object.keys(obj).forEach((key) => {
176 const parameterKey = prefix ? prefix + "[" + key + "]" : key;
177 const value = obj[key];
178 if (typeof value === "object") {
179 parameters.push(serialize(value, parameterKey));
c9c0b67c 180 }
665fa171
AE
181 else {
182 parameters.push(encodeURIComponent(parameterKey) + "=" + encodeURIComponent(value));
183 }
184 });
6ab9b916 185 return parameters.join("&");
c9c0b67c 186 }
c9c0b67c
AE
187 /**
188 * Triggers a custom or built-in event.
189 */
190 function triggerEvent(element, eventName) {
3bc7d8d7
AE
191 if (eventName === "click" && element instanceof HTMLElement) {
192 element.click();
193 return;
194 }
f10d9af6
AE
195 const event = new Event(eventName, {
196 bubbles: true,
197 cancelable: true,
198 });
c9c0b67c
AE
199 element.dispatchEvent(event);
200 }
c9c0b67c
AE
201 /**
202 * Returns the unique prefix for the localStorage.
203 */
204 function getStoragePrefix() {
205 return _prefix;
206 }
378fa43c
AE
207 /**
208 * Interprets a string value as a boolean value similar to the behavior of the
209 * legacy functions `elAttrBool()` and `elDataBool()`.
210 */
211 function stringToBool(value) {
6ab9b916 212 return value === "1" || value === "true";
378fa43c 213 }
8f85c642
AE
214 /**
215 * A function that emits a side effect and does not return anything.
216 *
217 * @see https://github.com/chodorowicz/ts-debounce/blob/62f30f2c3379b7b5e778fb1793e1fbfa17354894/src/index.ts
218 */
219 function debounce(func, waitMilliseconds = 50, options = {
220 isImmediate: false,
221 }) {
222 let timeoutId;
223 return function (...args) {
665fa171 224 const doLater = () => {
8f85c642
AE
225 timeoutId = undefined;
226 if (!options.isImmediate) {
665fa171 227 func.apply(this, args);
8f85c642
AE
228 }
229 };
230 const shouldCallNow = options.isImmediate && timeoutId === undefined;
231 if (timeoutId !== undefined) {
232 clearTimeout(timeoutId);
233 }
234 timeoutId = setTimeout(doLater, waitMilliseconds);
235 if (shouldCallNow) {
665fa171 236 func.apply(this, args);
8f85c642
AE
237 }
238 };
239 }
fd6a70e0
AE
240 /**
241 * @deprecated 6.0
242 */
cc2639d3 243 function enableLegacyInheritance(legacyClass) {
fd6a70e0
AE
244 // This MUST NOT be an error to prevent bricking installations during the upgrade.
245 console.error("Relying on the legacy inheritance is no longer supported. Please migrate your code to use ES6 classes and inheritance.", legacyClass);
cc2639d3 246 }
46cd659c 247 function getXsrfToken() {
9a8134d3
TD
248 const cookies = document.cookie.split(";").map((c) => c.trim());
249 const xsrfToken = cookies.find((c) => c.startsWith("XSRF-TOKEN="));
250 if (xsrfToken === undefined) {
251 return "COOKIE_NOT_FOUND";
252 }
253 const [_key, value] = xsrfToken.split(/=/, 2);
254 return decodeURIComponent(value.trim());
46cd659c 255 }
c9c0b67c 256});