Updating minified JavaScript files
authorwoltlab.com <woltlab@woltlab.com>
Sat, 29 Jun 2019 20:35:37 +0000 (20:35 +0000)
committerwoltlab.com <woltlab@woltlab.com>
Sat, 29 Jun 2019 20:35:37 +0000 (20:35 +0000)
wcfsetup/install/files/js/WoltLabSuite.Core.min.js
wcfsetup/install/files/js/WoltLabSuite.Core.tiny.min.js

index 59d479a8f3758df264db6438d1ea95391aa828fe..ec6744bf988a98d77cc880417cc03025e5522a26 100644 (file)
 
 
 // WoltLabSuite.Core.min.js
-/**
- * @license alameda 1.2.0 Copyright jQuery Foundation and other contributors.
- * Released under MIT license, https://github.com/requirejs/alameda/blob/master/LICENSE
- */
-// Going sloppy because loader plugin execs may depend on non-strict execution.
-/*jslint sloppy: true, nomen: true, regexp: true */
-/*global document, navigator, importScripts, Promise, setTimeout */
-
-var requirejs, require, define;
-(function (global, Promise, undef) {
-  if (!Promise) {
-    throw new Error('No Promise implementation available');
-  }
-
-  var topReq, dataMain, src, subPath,
-    bootstrapConfig = requirejs || require,
-    hasOwn = Object.prototype.hasOwnProperty,
-    contexts = {},
-    queue = [],
-    currDirRegExp = /^\.\//,
-    urlRegExp = /^\/|\:|\?|\.js$/,
-    commentRegExp = /\/\*[\s\S]*?\*\/|([^:"'=]|^)\/\/.*$/mg,
-    cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,
-    jsSuffixRegExp = /\.js$/,
-    slice = Array.prototype.slice;
-
-  if (typeof requirejs === 'function') {
-    return;
-  }
-
-  var asap = Promise.resolve(undefined);
-
-  // Could match something like ')//comment', do not lose the prefix to comment.
-  function commentReplace(match, singlePrefix) {
-    return singlePrefix || '';
-  }
-
-  function hasProp(obj, prop) {
-    return hasOwn.call(obj, prop);
-  }
-
-  function getOwn(obj, prop) {
-    return obj && hasProp(obj, prop) && obj[prop];
-  }
-
-  function obj() {
-    return Object.create(null);
-  }
-
-  /**
-   * Cycles over properties in an object and calls a function for each
-   * property value. If the function returns a truthy value, then the
-   * iteration is stopped.
-   */
-  function eachProp(obj, func) {
-    var prop;
-    for (prop in obj) {
-      if (hasProp(obj, prop)) {
-        if (func(obj[prop], prop)) {
-          break;
-        }
-      }
-    }
-  }
-
-  /**
-   * Simple function to mix in properties from source into target,
-   * but only if target does not already have a property of the same name.
-   */
-  function mixin(target, source, force, deepStringMixin) {
-    if (source) {
-      eachProp(source, function (value, prop) {
-        if (force || !hasProp(target, prop)) {
-          if (deepStringMixin && typeof value === 'object' && value &&
-            !Array.isArray(value) && typeof value !== 'function' &&
-            !(value instanceof RegExp)) {
-
-            if (!target[prop]) {
-              target[prop] = {};
-            }
-            mixin(target[prop], value, force, deepStringMixin);
-          } else {
-            target[prop] = value;
-          }
-        }
-      });
-    }
-    return target;
-  }
-
-  // Allow getting a global that expressed in
-  // dot notation, like 'a.b.c'.
-  function getGlobal(value) {
-    if (!value) {
-      return value;
-    }
-    var g = global;
-    value.split('.').forEach(function (part) {
-      g = g[part];
-    });
-    return g;
-  }
-
-  function newContext(contextName) {
-    var req, main, makeMap, callDep, handlers, checkingLater, load, context,
-      defined = obj(),
-      waiting = obj(),
-      config = {
-        // Defaults. Do not set a default for map
-        // config to speed up normalize(), which
-        // will run faster if there is no default.
-        waitSeconds: 7,
-        baseUrl: './',
-        paths: {},
-        bundles: {},
-        pkgs: {},
-        shim: {},
-        config: {}
-      },
-      mapCache = obj(),
-      requireDeferreds = [],
-      deferreds = obj(),
-      calledDefine = obj(),
-      calledPlugin = obj(),
-      loadCount = 0,
-      startTime = (new Date()).getTime(),
-      errCount = 0,
-      trackedErrors = obj(),
-      urlFetched = obj(),
-      bundlesMap = obj(),
-      asyncResolve = Promise.resolve();
-
-    /**
-     * Trims the . and .. from an array of path segments.
-     * It will keep a leading path segment if a .. will become
-     * the first path segment, to help with module name lookups,
-     * which act like paths, but can be remapped. But the end result,
-     * all paths that use this function should look normalized.
-     * NOTE: this method MODIFIES the input array.
-     * @param {Array} ary the array of path segments.
-     */
-    function trimDots(ary) {
-      var i, part, length = ary.length;
-      for (i = 0; i < length; i++) {
-        part = ary[i];
-        if (part === '.') {
-          ary.splice(i, 1);
-          i -= 1;
-        } else if (part === '..') {
-          // If at the start, or previous value is still ..,
-          // keep them so that when converted to a path it may
-          // still work when converted to a path, even though
-          // as an ID it is less than ideal. In larger point
-          // releases, may be better to just kick out an error.
-          if (i === 0 || (i === 1 && ary[2] === '..') || ary[i - 1] === '..') {
-            continue;
-          } else if (i > 0) {
-            ary.splice(i - 1, 2);
-            i -= 2;
-          }
-        }
-      }
-    }
-
-    /**
-     * Given a relative module name, like ./something, normalize it to
-     * a real name that can be mapped to a path.
-     * @param {String} name the relative name
-     * @param {String} baseName a real name that the name arg is relative
-     * to.
-     * @param {Boolean} applyMap apply the map config to the value. Should
-     * only be done if this normalization is for a dependency ID.
-     * @returns {String} normalized name
-     */
-    function normalize(name, baseName, applyMap) {
-      var pkgMain, mapValue, nameParts, i, j, nameSegment, lastIndex,
-        foundMap, foundI, foundStarMap, starI,
-        baseParts = baseName && baseName.split('/'),
-        normalizedBaseParts = baseParts,
-        map = config.map,
-        starMap = map && map['*'];
-
-
-      //Adjust any relative paths.
-      if (name) {
-        name = name.split('/');
-        lastIndex = name.length - 1;
-
-        // If wanting node ID compatibility, strip .js from end
-        // of IDs. Have to do this here, and not in nameToUrl
-        // because node allows either .js or non .js to map
-        // to same file.
-        if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
-          name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
-        }
-
-        // Starts with a '.' so need the baseName
-        if (name[0].charAt(0) === '.' && baseParts) {
-          //Convert baseName to array, and lop off the last part,
-          //so that . matches that 'directory' and not name of the baseName's
-          //module. For instance, baseName of 'one/two/three', maps to
-          //'one/two/three.js', but we want the directory, 'one/two' for
-          //this normalization.
-          normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
-          name = normalizedBaseParts.concat(name);
-        }
-
-        trimDots(name);
-        name = name.join('/');
-      }
-
-      // Apply map config if available.
-      if (applyMap && map && (baseParts || starMap)) {
-        nameParts = name.split('/');
-
-        outerLoop: for (i = nameParts.length; i > 0; i -= 1) {
-          nameSegment = nameParts.slice(0, i).join('/');
-
-          if (baseParts) {
-            // Find the longest baseName segment match in the config.
-            // So, do joins on the biggest to smallest lengths of baseParts.
-            for (j = baseParts.length; j > 0; j -= 1) {
-              mapValue = getOwn(map, baseParts.slice(0, j).join('/'));
-
-              // baseName segment has config, find if it has one for
-              // this name.
-              if (mapValue) {
-                mapValue = getOwn(mapValue, nameSegment);
-                if (mapValue) {
-                  // Match, update name to the new value.
-                  foundMap = mapValue;
-                  foundI = i;
-                  break outerLoop;
-                }
-              }
-            }
-          }
-
-          // Check for a star map match, but just hold on to it,
-          // if there is a shorter segment match later in a matching
-          // config, then favor over this star map.
-          if (!foundStarMap && starMap && getOwn(starMap, nameSegment)) {
-            foundStarMap = getOwn(starMap, nameSegment);
-            starI = i;
-          }
-        }
-
-        if (!foundMap && foundStarMap) {
-          foundMap = foundStarMap;
-          foundI = starI;
-        }
-
-        if (foundMap) {
-          nameParts.splice(0, foundI, foundMap);
-          name = nameParts.join('/');
-        }
-      }
-
-      // If the name points to a package's name, use
-      // the package main instead.
-      pkgMain = getOwn(config.pkgs, name);
-
-      return pkgMain ? pkgMain : name;
-    }
-
-    function makeShimExports(value) {
-      function fn() {
-        var ret;
-        if (value.init) {
-          ret = value.init.apply(global, arguments);
-        }
-        return ret || (value.exports && getGlobal(value.exports));
-      }
-      return fn;
-    }
-
-    function takeQueue(anonId) {
-      var i, id, args, shim;
-      for (i = 0; i < queue.length; i += 1) {
-        // Peek to see if anon
-        if (typeof queue[i][0] !== 'string') {
-          if (anonId) {
-            queue[i].unshift(anonId);
-            anonId = undef;
-          } else {
-            // Not our anon module, stop.
-            break;
-          }
-        }
-        args = queue.shift();
-        id = args[0];
-        i -= 1;
-
-        if (!(id in defined) && !(id in waiting)) {
-          if (id in deferreds) {
-            main.apply(undef, args);
-          } else {
-            waiting[id] = args;
-          }
-        }
-      }
-
-      // if get to the end and still have anonId, then could be
-      // a shimmed dependency.
-      if (anonId) {
-        shim = getOwn(config.shim, anonId) || {};
-        main(anonId, shim.deps || [], shim.exportsFn);
-      }
-    }
-
-    function makeRequire(relName, topLevel) {
-      var req = function (deps, callback, errback, alt) {
-        var name, cfg;
-
-        if (topLevel) {
-          takeQueue();
-        }
-
-        if (typeof deps === "string") {
-          if (handlers[deps]) {
-            return handlers[deps](relName);
-          }
-          // Just return the module wanted. In this scenario, the
-          // deps arg is the module name, and second arg (if passed)
-          // is just the relName.
-          // Normalize module name, if it contains . or ..
-          name = makeMap(deps, relName, true).id;
-          if (!(name in defined)) {
-            throw new Error('Not loaded: ' + name);
-          }
-          return defined[name];
-        } else if (deps && !Array.isArray(deps)) {
-          // deps is a config object, not an array.
-          cfg = deps;
-          deps = undef;
-
-          if (Array.isArray(callback)) {
-            // callback is an array, which means it is a dependency list.
-            // Adjust args if there are dependencies
-            deps = callback;
-            callback = errback;
-            errback = alt;
-          }
-
-          if (topLevel) {
-            // Could be a new context, so call returned require
-            return req.config(cfg)(deps, callback, errback);
-          }
-        }
-
-        // Support require(['a'])
-        callback = callback || function () {
-          // In case used later as a promise then value, return the
-          // arguments as an array.
-          return slice.call(arguments, 0);
-        };
-
-        // Complete async to maintain expected execution semantics.
-        return asyncResolve.then(function () {
-          // Grab any modules that were defined after a require call.
-          takeQueue();
-
-          return main(undef, deps || [], callback, errback, relName);
-        });
-      };
-
-      req.isBrowser = typeof document !== 'undefined' &&
-        typeof navigator !== 'undefined';
-
-      req.nameToUrl = function (moduleName, ext, skipExt) {
-        var paths, syms, i, parentModule, url,
-          parentPath, bundleId,
-          pkgMain = getOwn(config.pkgs, moduleName);
-
-        if (pkgMain) {
-          moduleName = pkgMain;
-        }
-
-        bundleId = getOwn(bundlesMap, moduleName);
-
-        if (bundleId) {
-          return req.nameToUrl(bundleId, ext, skipExt);
-        }
-
-        // If a colon is in the URL, it indicates a protocol is used and it is
-        // just an URL to a file, or if it starts with a slash, contains a query
-        // arg (i.e. ?) or ends with .js, then assume the user meant to use an
-        // url and not a module id. The slash is important for protocol-less
-        // URLs as well as full paths.
-        if (urlRegExp.test(moduleName)) {
-          // Just a plain path, not module name lookup, so just return it.
-          // Add extension if it is included. This is a bit wonky, only non-.js
-          // things pass an extension, this method probably needs to be
-          // reworked.
-          url = moduleName + (ext || '');
-        } else {
-          // A module that needs to be converted to a path.
-          paths = config.paths;
-
-          syms = moduleName.split('/');
-          // For each module name segment, see if there is a path
-          // registered for it. Start with most specific name
-          // and work up from it.
-          for (i = syms.length; i > 0; i -= 1) {
-            parentModule = syms.slice(0, i).join('/');
-
-            parentPath = getOwn(paths, parentModule);
-            if (parentPath) {
-              // If an array, it means there are a few choices,
-              // Choose the one that is desired
-              if (Array.isArray(parentPath)) {
-                parentPath = parentPath[0];
-              }
-              syms.splice(0, i, parentPath);
-              break;
-            }
-          }
-
-          // Join the path parts together, then figure out if baseUrl is needed.
-          url = syms.join('/');
-          url += (ext || (/^data\:|^blob\:|\?/.test(url) || skipExt ? '' : '.js'));
-          url = (url.charAt(0) === '/' ||
-                url.match(/^[\w\+\.\-]+:/) ? '' : config.baseUrl) + url;
-        }
-
-        return config.urlArgs && !/^blob\:/.test(url) ?
-               url + config.urlArgs(moduleName, url) : url;
-      };
-
-      /**
-       * Converts a module name + .extension into an URL path.
-       * *Requires* the use of a module name. It does not support using
-       * plain URLs like nameToUrl.
-       */
-      req.toUrl = function (moduleNamePlusExt) {
-        var ext,
-          index = moduleNamePlusExt.lastIndexOf('.'),
-          segment = moduleNamePlusExt.split('/')[0],
-          isRelative = segment === '.' || segment === '..';
-
-        // Have a file extension alias, and it is not the
-        // dots from a relative path.
-        if (index !== -1 && (!isRelative || index > 1)) {
-          ext = moduleNamePlusExt.substring(index, moduleNamePlusExt.length);
-          moduleNamePlusExt = moduleNamePlusExt.substring(0, index);
-        }
-
-        return req.nameToUrl(normalize(moduleNamePlusExt, relName), ext, true);
-      };
-
-      req.defined = function (id) {
-        return makeMap(id, relName, true).id in defined;
-      };
-
-      req.specified = function (id) {
-        id = makeMap(id, relName, true).id;
-        return id in defined || id in deferreds;
-      };
-
-      return req;
-    }
-
-    function resolve(name, d, value) {
-      if (name) {
-        defined[name] = value;
-        if (requirejs.onResourceLoad) {
-          requirejs.onResourceLoad(context, d.map, d.deps);
-        }
-      }
-      d.finished = true;
-      d.resolve(value);
-    }
-
-    function reject(d, err) {
-      d.finished = true;
-      d.rejected = true;
-      d.reject(err);
-    }
-
-    function makeNormalize(relName) {
-      return function (name) {
-        return normalize(name, relName, true);
-      };
-    }
-
-    function defineModule(d) {
-      d.factoryCalled = true;
-
-      var ret,
-        name = d.map.id;
-
-      try {
-        ret = context.execCb(name, d.factory, d.values, defined[name]);
-      } catch(err) {
-        return reject(d, err);
-      }
-
-      if (name) {
-        // Favor return value over exports. If node/cjs in play,
-        // then will not have a return value anyway. Favor
-        // module.exports assignment over exports object.
-        if (ret === undef) {
-          if (d.cjsModule) {
-            ret = d.cjsModule.exports;
-          } else if (d.usingExports) {
-            ret = defined[name];
-          }
-        }
-      } else {
-        // Remove the require deferred from the list to
-        // make cycle searching faster. Do not need to track
-        // it anymore either.
-        requireDeferreds.splice(requireDeferreds.indexOf(d), 1);
-      }
-      resolve(name, d, ret);
-    }
-
-    // This method is attached to every module deferred,
-    // so the "this" in here is the module deferred object.
-    function depFinished(val, i) {
-      if (!this.rejected && !this.depDefined[i]) {
-        this.depDefined[i] = true;
-        this.depCount += 1;
-        this.values[i] = val;
-        if (!this.depending && this.depCount === this.depMax) {
-          defineModule(this);
-        }
-      }
-    }
-
-    function makeDefer(name, calculatedMap) {
-      var d = {};
-      d.promise = new Promise(function (resolve, reject) {
-        d.resolve = resolve;
-        d.reject = function(err) {
-          if (!name) {
-          requireDeferreds.splice(requireDeferreds.indexOf(d), 1);
-          }
-          reject(err);
-        };
-      });
-      d.map = name ? (calculatedMap || makeMap(name)) : {};
-      d.depCount = 0;
-      d.depMax = 0;
-      d.values = [];
-      d.depDefined = [];
-      d.depFinished = depFinished;
-      if (d.map.pr) {
-        // Plugin resource ID, implicitly
-        // depends on plugin. Track it in deps
-        // so cycle breaking can work
-        d.deps = [makeMap(d.map.pr)];
-      }
-      return d;
-    }
-
-    function getDefer(name, calculatedMap) {
-      var d;
-      if (name) {
-        d = (name in deferreds) && deferreds[name];
-        if (!d) {
-          d = deferreds[name] = makeDefer(name, calculatedMap);
-        }
-      } else {
-        d = makeDefer();
-        requireDeferreds.push(d);
-      }
-      return d;
-    }
-
-    function makeErrback(d, name) {
-      return function (err) {
-        if (!d.rejected) {
-          if (!err.dynaId) {
-            err.dynaId = 'id' + (errCount += 1);
-            err.requireModules = [name];
-          }
-          reject(d, err);
-        }
-      };
-    }
-
-    function waitForDep(depMap, relName, d, i) {
-      d.depMax += 1;
-
-      // Do the fail at the end to catch errors
-      // in the then callback execution.
-      callDep(depMap, relName).then(function (val) {
-        d.depFinished(val, i);
-      }, makeErrback(d, depMap.id)).catch(makeErrback(d, d.map.id));
-    }
-
-    function makeLoad(id) {
-      var fromTextCalled;
-      function load(value) {
-        // Protect against older plugins that call load after
-        // calling load.fromText
-        if (!fromTextCalled) {
-          resolve(id, getDefer(id), value);
-        }
-      }
-
-      load.error = function (err) {
-        getDefer(id).reject(err);
-      };
-
-      load.fromText = function (text, textAlt) {
-        /*jslint evil: true */
-        var d = getDefer(id),
-          map = makeMap(makeMap(id).n),
-           plainId = map.id;
-
-        fromTextCalled = true;
-
-        // Set up the factory just to be a return of the value from
-        // plainId.
-        d.factory = function (p, val) {
-          return val;
-        };
-
-        // As of requirejs 2.1.0, support just passing the text, to reinforce
-        // fromText only being called once per resource. Still
-        // support old style of passing moduleName but discard
-        // that moduleName in favor of the internal ref.
-        if (textAlt) {
-          text = textAlt;
-        }
-
-        // Transfer any config to this other module.
-        if (hasProp(config.config, id)) {
-          config.config[plainId] = config.config[id];
-        }
-
-        try {
-          req.exec(text);
-        } catch (e) {
-          reject(d, new Error('fromText eval for ' + plainId +
-                  ' failed: ' + e));
-        }
-
-        // Execute any waiting define created by the plainId
-        takeQueue(plainId);
-
-        // Mark this as a dependency for the plugin
-        // resource
-        d.deps = [map];
-        waitForDep(map, null, d, d.deps.length);
-      };
-
-      return load;
-    }
-
-    load = typeof importScripts === 'function' ?
-        function (map) {
-          var url = map.url;
-          if (urlFetched[url]) {
-            return;
-          }
-          urlFetched[url] = true;
-
-          // Ask for the deferred so loading is triggered.
-          // Do this before loading, since loading is sync.
-          getDefer(map.id);
-          importScripts(url);
-          takeQueue(map.id);
-        } :
-        function (map) {
-          var script,
-            id = map.id,
-            url = map.url;
-
-          if (urlFetched[url]) {
-            return;
-          }
-          urlFetched[url] = true;
-
-          script = document.createElement('script');
-          script.setAttribute('data-requiremodule', id);
-          script.type = config.scriptType || 'text/javascript';
-          script.charset = 'utf-8';
-          script.async = true;
-
-          loadCount += 1;
-
-          script.addEventListener('load', function () {
-            loadCount -= 1;
-            takeQueue(id);
-          }, false);
-          script.addEventListener('error', function () {
-            loadCount -= 1;
-            var err,
-              pathConfig = getOwn(config.paths, id);
-            if (pathConfig && Array.isArray(pathConfig) &&
-                pathConfig.length > 1) {
-              script.parentNode.removeChild(script);
-              // Pop off the first array value, since it failed, and
-              // retry
-              pathConfig.shift();
-              var d = getDefer(id);
-              d.map = makeMap(id);
-              // mapCache will have returned previous map value, update the
-              // url, which will also update mapCache value.
-              d.map.url = req.nameToUrl(id);
-              load(d.map);
-            } else {
-              err = new Error('Load failed: ' + id + ': ' + script.src);
-              err.requireModules = [id];
-              getDefer(id).reject(err);
-            }
-          }, false);
-
-          script.src = url;
-
-          // If the script is cached, IE10 executes the script body and the
-          // onload handler synchronously here.  That's a spec violation,
-          // so be sure to do this asynchronously.
-          if (document.documentMode === 10) {
-            asap.then(function() {
-              document.head.appendChild(script);
-            });
-          } else {
-            document.head.appendChild(script);
-          }
-        };
-
-    function callPlugin(plugin, map, relName) {
-      plugin.load(map.n, makeRequire(relName), makeLoad(map.id), config);
-    }
-
-    callDep = function (map, relName) {
-      var args, bundleId,
-        name = map.id,
-        shim = config.shim[name];
-
-      if (name in waiting) {
-        args = waiting[name];
-        delete waiting[name];
-        main.apply(undef, args);
-      } else if (!(name in deferreds)) {
-        if (map.pr) {
-          // If a bundles config, then just load that file instead to
-          // resolve the plugin, as it is built into that bundle.
-          if ((bundleId = getOwn(bundlesMap, name))) {
-            map.url = req.nameToUrl(bundleId);
-            load(map);
-          } else {
-            return callDep(makeMap(map.pr)).then(function (plugin) {
-              // Redo map now that plugin is known to be loaded
-              var newMap = map.prn ? map : makeMap(name, relName, true),
-                newId = newMap.id,
-                shim = getOwn(config.shim, newId);
-
-              // Make sure to only call load once per resource. Many
-              // calls could have been queued waiting for plugin to load.
-              if (!(newId in calledPlugin)) {
-                calledPlugin[newId] = true;
-                if (shim && shim.deps) {
-                  req(shim.deps, function () {
-                    callPlugin(plugin, newMap, relName);
-                  });
-                } else {
-                  callPlugin(plugin, newMap, relName);
-                }
-              }
-              return getDefer(newId).promise;
-            });
-          }
-        } else if (shim && shim.deps) {
-          req(shim.deps, function () {
-            load(map);
-          });
-        } else {
-          load(map);
-        }
-      }
-
-      return getDefer(name).promise;
-    };
-
-    // Turns a plugin!resource to [plugin, resource]
-    // with the plugin being undefined if the name
-    // did not have a plugin prefix.
-    function splitPrefix(name) {
-      var prefix,
-        index = name ? name.indexOf('!') : -1;
-      if (index > -1) {
-        prefix = name.substring(0, index);
-        name = name.substring(index + 1, name.length);
-      }
-      return [prefix, name];
-    }
-
-    /**
-     * Makes a name map, normalizing the name, and using a plugin
-     * for normalization if necessary. Grabs a ref to plugin
-     * too, as an optimization.
-     */
-    makeMap = function (name, relName, applyMap) {
-      if (typeof name !== 'string') {
-        return name;
-      }
-
-      var plugin, url, parts, prefix, result, prefixNormalized,
-        cacheKey = name + ' & ' + (relName || '') + ' & ' + !!applyMap;
-
-      parts = splitPrefix(name);
-      prefix = parts[0];
-      name = parts[1];
-
-      if (!prefix && (cacheKey in mapCache)) {
-        return mapCache[cacheKey];
-      }
-
-      if (prefix) {
-        prefix = normalize(prefix, relName, applyMap);
-        plugin = (prefix in defined) && defined[prefix];
-      }
-
-      // Normalize according
-      if (prefix) {
-        if (plugin && plugin.normalize) {
-          name = plugin.normalize(name, makeNormalize(relName));
-          prefixNormalized = true;
-        } else {
-          // If nested plugin references, then do not try to
-          // normalize, as it will not normalize correctly. This
-          // places a restriction on resourceIds, and the longer
-          // term solution is not to normalize until plugins are
-          // loaded and all normalizations to allow for async
-          // loading of a loader plugin. But for now, fixes the
-          // common uses. Details in requirejs#1131
-          name = name.indexOf('!') === -1 ?
-                   normalize(name, relName, applyMap) :
-                   name;
-        }
-      } else {
-        name = normalize(name, relName, applyMap);
-        parts = splitPrefix(name);
-        prefix = parts[0];
-        name = parts[1];
-
-        url = req.nameToUrl(name);
-      }
-
-      // Using ridiculous property names for space reasons
-      result = {
-        id: prefix ? prefix + '!' + name : name, // fullName
-        n: name,
-        pr: prefix,
-        url: url,
-        prn: prefix && prefixNormalized
-      };
-
-      if (!prefix) {
-        mapCache[cacheKey] = result;
-      }
-
-      return result;
-    };
-
-    handlers = {
-      require: function (name) {
-        return makeRequire(name);
-      },
-      exports: function (name) {
-        var e = defined[name];
-        if (typeof e !== 'undefined') {
-          return e;
-        } else {
-          return (defined[name] = {});
-        }
-      },
-      module: function (name) {
-        return {
-          id: name,
-          uri: '',
-          exports: handlers.exports(name),
-          config: function () {
-            return getOwn(config.config, name) || {};
-          }
-        };
-      }
-    };
-
-    function breakCycle(d, traced, processed) {
-      var id = d.map.id;
-
-      traced[id] = true;
-      if (!d.finished && d.deps) {
-        d.deps.forEach(function (depMap) {
-          var depId = depMap.id,
-            dep = !hasProp(handlers, depId) && getDefer(depId, depMap);
-
-          // Only force things that have not completed
-          // being defined, so still in the registry,
-          // and only if it has not been matched up
-          // in the module already.
-          if (dep && !dep.finished && !processed[depId]) {
-            if (hasProp(traced, depId)) {
-              d.deps.forEach(function (depMap, i) {
-                if (depMap.id === depId) {
-                  d.depFinished(defined[depId], i);
-                }
-              });
-            } else {
-              breakCycle(dep, traced, processed);
-            }
-          }
-        });
-      }
-      processed[id] = true;
-    }
-
-    function check(d) {
-      var err, mid, dfd,
-        notFinished = [],
-        waitInterval = config.waitSeconds * 1000,
-        // It is possible to disable the wait interval by using waitSeconds 0.
-        expired = waitInterval &&
-                  (startTime + waitInterval) < (new Date()).getTime();
-
-    if (loadCount === 0) {
-        // If passed in a deferred, it is for a specific require call.
-        // Could be a sync case that needs resolution right away.
-        // Otherwise, if no deferred, means it was the last ditch
-        // timeout-based check, so check all waiting require deferreds.
-        if (d) {
-          if (!d.finished) {
-            breakCycle(d, {}, {});
-          }
-        } else if (requireDeferreds.length) {
-          requireDeferreds.forEach(function (d) {
-            breakCycle(d, {}, {});
-          });
-        }
-      }
-
-      // If still waiting on loads, and the waiting load is something
-      // other than a plugin resource, or there are still outstanding
-      // scripts, then just try back later.
-      if (expired) {
-        // If wait time expired, throw error of unloaded modules.
-        for (mid in deferreds) {
-          dfd = deferreds[mid];
-          if (!dfd.finished) {
-            notFinished.push(dfd.map.id);
-          }
-        }
-        err = new Error('Timeout for modules: ' + notFinished);
-        err.requireModules = notFinished;
-        req.onError(err);
-      } else if (loadCount || requireDeferreds.length) {
-        // Something is still waiting to load. Wait for it, but only
-        // if a later check is not already scheduled. Using setTimeout
-        // because want other things in the event loop to happen,
-        // to help in dependency resolution, and this is really a
-        // last ditch check, mostly for detecting timeouts (cycles
-        // should come through the main() use of check()), so it can
-        // wait a bit before doing the final check.
-        if (!checkingLater) {
-          checkingLater = true;
-          setTimeout(function () {
-            checkingLater = false;
-            check();
-          }, 70);
-        }
-      }
-    }
-
-    // Used to break out of the promise try/catch chains.
-    function delayedError(e) {
-      setTimeout(function () {
-        if (!e.dynaId || !trackedErrors[e.dynaId]) {
-          trackedErrors[e.dynaId] = true;
-          req.onError(e);
-        }
-      });
-      return e;
-    }
-
-    main = function (name, deps, factory, errback, relName) {
-      if (name) {
-        // Only allow main calling once per module.
-        if (name in calledDefine) {
-          return;
-        }
-        calledDefine[name] = true;
-      }
-
-      var d = getDefer(name);
-
-      // This module may not have dependencies
-      if (deps && !Array.isArray(deps)) {
-        // deps is not an array, so probably means
-        // an object literal or factory function for
-        // the value. Adjust args.
-        factory = deps;
-        deps = [];
-      }
-
-      // Create fresh array instead of modifying passed in value.
-      deps = deps ? slice.call(deps, 0) : null;
-
-      if (!errback) {
-        if (hasProp(config, 'defaultErrback')) {
-          if (config.defaultErrback) {
-            errback = config.defaultErrback;
-          }
-        } else {
-          errback = delayedError;
-        }
-      }
-
-      if (errback) {
-         d.promise.catch(errback);
-      }
-
-      // Use name if no relName
-      relName = relName || name;
-
-      // Call the factory to define the module, if necessary.
-      if (typeof factory === 'function') {
-
-        if (!deps.length && factory.length) {
-          // Remove comments from the callback string,
-          // look for require calls, and pull them into the dependencies,
-          // but only if there are function args.
-          factory
-            .toString()
-            .replace(commentRegExp, commentReplace)
-            .replace(cjsRequireRegExp, function (match, dep) {
-              deps.push(dep);
-            });
-
-          // May be a CommonJS thing even without require calls, but still
-          // could use exports, and module. Avoid doing exports and module
-          // work though if it just needs require.
-          // REQUIRES the function to expect the CommonJS variables in the
-          // order listed below.
-          deps = (factory.length === 1 ?
-              ['require'] :
-              ['require', 'exports', 'module']).concat(deps);
-        }
-
-        // Save info for use later.
-        d.factory = factory;
-        d.deps = deps;
-
-        d.depending = true;
-        deps.forEach(function (depName, i) {
-          var depMap;
-          deps[i] = depMap = makeMap(depName, relName, true);
-          depName = depMap.id;
-
-          // Fast path CommonJS standard dependencies.
-          if (depName === "require") {
-            d.values[i] = handlers.require(name);
-          } else if (depName === "exports") {
-            // CommonJS module spec 1.1
-            d.values[i] = handlers.exports(name);
-            d.usingExports = true;
-          } else if (depName === "module") {
-            // CommonJS module spec 1.1
-            d.values[i] = d.cjsModule = handlers.module(name);
-          } else if (depName === undefined) {
-            d.values[i] = undefined;
-          } else {
-            waitForDep(depMap, relName, d, i);
-          }
-        });
-        d.depending = false;
-
-        // Some modules just depend on the require, exports, modules, so
-        // trigger their definition here if so.
-        if (d.depCount === d.depMax) {
-          defineModule(d);
-        }
-      } else if (name) {
-        // May just be an object definition for the module. Only
-        // worry about defining if have a module name.
-        resolve(name, d, factory);
-      }
-
-      startTime = (new Date()).getTime();
-
-      if (!name) {
-        check(d);
-      }
-
-      return d.promise;
-    };
-
-    req = makeRequire(null, true);
-
-    /*
-     * Just drops the config on the floor, but returns req in case
-     * the config return value is used.
-     */
-    req.config = function (cfg) {
-      if (cfg.context && cfg.context !== contextName) {
-        var existingContext = getOwn(contexts, cfg.context);
-        if (existingContext) {
-          return existingContext.req.config(cfg);
-        } else {
-          return newContext(cfg.context).config(cfg);
-        }
-      }
-
-      // Since config changed, mapCache may not be valid any more.
-      mapCache = obj();
-
-      // Make sure the baseUrl ends in a slash.
-      if (cfg.baseUrl) {
-        if (cfg.baseUrl.charAt(cfg.baseUrl.length - 1) !== '/') {
-          cfg.baseUrl += '/';
-        }
-      }
-
-      // Convert old style urlArgs string to a function.
-      if (typeof cfg.urlArgs === 'string') {
-        var urlArgs = cfg.urlArgs;
-        cfg.urlArgs = function(id, url) {
-          return (url.indexOf('?') === -1 ? '?' : '&') + urlArgs;
-        };
-      }
-
-      // Save off the paths and packages since they require special processing,
-      // they are additive.
-      var shim = config.shim,
-        objs = {
-          paths: true,
-          bundles: true,
-          config: true,
-          map: true
-        };
-
-      eachProp(cfg, function (value, prop) {
-        if (objs[prop]) {
-          if (!config[prop]) {
-            config[prop] = {};
-          }
-          mixin(config[prop], value, true, true);
-        } else {
-          config[prop] = value;
-        }
-      });
-
-      // Reverse map the bundles
-      if (cfg.bundles) {
-        eachProp(cfg.bundles, function (value, prop) {
-          value.forEach(function (v) {
-            if (v !== prop) {
-              bundlesMap[v] = prop;
-            }
-          });
-        });
-      }
-
-      // Merge shim
-      if (cfg.shim) {
-        eachProp(cfg.shim, function (value, id) {
-          // Normalize the structure
-          if (Array.isArray(value)) {
-            value = {
-              deps: value
-            };
-          }
-          if ((value.exports || value.init) && !value.exportsFn) {
-            value.exportsFn = makeShimExports(value);
-          }
-          shim[id] = value;
-        });
-        config.shim = shim;
-      }
-
-      // Adjust packages if necessary.
-      if (cfg.packages) {
-        cfg.packages.forEach(function (pkgObj) {
-          var location, name;
-
-          pkgObj = typeof pkgObj === 'string' ? { name: pkgObj } : pkgObj;
-
-          name = pkgObj.name;
-          location = pkgObj.location;
-          if (location) {
-            config.paths[name] = pkgObj.location;
-          }
-
-          // Save pointer to main module ID for pkg name.
-          // Remove leading dot in main, so main paths are normalized,
-          // and remove any trailing .js, since different package
-          // envs have different conventions: some use a module name,
-          // some use a file name.
-          config.pkgs[name] = pkgObj.name + '/' + (pkgObj.main || 'main')
-                 .replace(currDirRegExp, '')
-                 .replace(jsSuffixRegExp, '');
-        });
-      }
-
-      // If a deps array or a config callback is specified, then call
-      // require with those args. This is useful when require is defined as a
-      // config object before require.js is loaded.
-      if (cfg.deps || cfg.callback) {
-        req(cfg.deps, cfg.callback);
-      }
-
-      return req;
-    };
-
-    req.onError = function (err) {
-      throw err;
-    };
-
-    context = {
-      id: contextName,
-      defined: defined,
-      waiting: waiting,
-      config: config,
-      deferreds: deferreds,
-      req: req,
-      execCb: function execCb(name, callback, args, exports) {
-        return callback.apply(exports, args);
-      }
-    };
-
-    contexts[contextName] = context;
-
-    return req;
-  }
-
-  requirejs = topReq = newContext('_');
-
-  if (typeof require !== 'function') {
-    require = topReq;
-  }
-
-  /**
-   * Executes the text. Normally just uses eval, but can be modified
-   * to use a better, environment-specific call. Only used for transpiling
-   * loader plugins, not for plain JS modules.
-   * @param {String} text the text to execute/evaluate.
-   */
-  topReq.exec = function (text) {
-    /*jslint evil: true */
-    return eval(text);
-  };
-
-  topReq.contexts = contexts;
-
-  define = function () {
-    queue.push(slice.call(arguments, 0));
-  };
-
-  define.amd = {
-    jQuery: true
-  };
-
-  if (bootstrapConfig) {
-    topReq.config(bootstrapConfig);
-  }
-
-  // data-main support.
-  if (topReq.isBrowser && !contexts._.config.skipDataMain) {
-    dataMain = document.querySelectorAll('script[data-main]')[0];
-    dataMain = dataMain && dataMain.getAttribute('data-main');
-    if (dataMain) {
-      // Strip off any trailing .js since dataMain is now
-      // like a module name.
-      dataMain = dataMain.replace(jsSuffixRegExp, '');
-
-      // Set final baseUrl if there is not already an explicit one,
-      // but only do so if the data-main value is not a loader plugin
-      // module ID.
-      if ((!bootstrapConfig || !bootstrapConfig.baseUrl) &&
-          dataMain.indexOf('!') === -1) {
-        // Pull off the directory of data-main for use as the
-        // baseUrl.
-        src = dataMain.split('/');
-        dataMain = src.pop();
-        subPath = src.length ? src.join('/')  + '/' : './';
-
-        topReq.config({baseUrl: subPath});
-      }
-
-      topReq([dataMain]);
-    }
-  }
-}(this, (typeof Promise !== 'undefined' ? Promise : undefined)));
-
-define("requireLib", function(){});
-
-//noinspection JSUnresolvedVariable
-requirejs.config({
-       paths: {
-               enquire: '3rdParty/enquire',
-               favico: '3rdParty/favico',
-               'perfect-scrollbar': '3rdParty/perfect-scrollbar',
-               'Pica': '3rdParty/pica',
-               prism: '3rdParty/prism',
-       },
-       shim: {
-               enquire: { exports: 'enquire' },
-               favico: { exports: 'Favico' },
-               'perfect-scrollbar': { exports: 'PerfectScrollbar' }
-       },
-       map: {
-               '*': {
-                       'Ajax': 'WoltLabSuite/Core/Ajax',
-                       'AjaxJsonp': 'WoltLabSuite/Core/Ajax/Jsonp',
-                       'AjaxRequest': 'WoltLabSuite/Core/Ajax/Request',
-                       'CallbackList': 'WoltLabSuite/Core/CallbackList',
-                       'ColorUtil': 'WoltLabSuite/Core/ColorUtil',
-                       'Core': 'WoltLabSuite/Core/Core',
-                       'DateUtil': 'WoltLabSuite/Core/Date/Util',
-                       'Devtools': 'WoltLabSuite/Core/Devtools',
-                       'Dictionary': 'WoltLabSuite/Core/Dictionary',
-                       'Dom/ChangeListener': 'WoltLabSuite/Core/Dom/Change/Listener',
-                       'Dom/Traverse': 'WoltLabSuite/Core/Dom/Traverse',
-                       'Dom/Util': 'WoltLabSuite/Core/Dom/Util',
-                       'Environment': 'WoltLabSuite/Core/Environment',
-                       'EventHandler': 'WoltLabSuite/Core/Event/Handler',
-                       'EventKey': 'WoltLabSuite/Core/Event/Key',
-                       'Language': 'WoltLabSuite/Core/Language',
-                       'List': 'WoltLabSuite/Core/List',
-                       'ObjectMap': 'WoltLabSuite/Core/ObjectMap',
-                       'Permission': 'WoltLabSuite/Core/Permission',
-                       'StringUtil': 'WoltLabSuite/Core/StringUtil',
-                       'Ui/Alignment': 'WoltLabSuite/Core/Ui/Alignment',
-                       'Ui/CloseOverlay': 'WoltLabSuite/Core/Ui/CloseOverlay',
-                       'Ui/Confirmation': 'WoltLabSuite/Core/Ui/Confirmation',
-                       'Ui/Dialog': 'WoltLabSuite/Core/Ui/Dialog',
-                       'Ui/Notification': 'WoltLabSuite/Core/Ui/Notification',
-                       'Ui/ReusableDropdown': 'WoltLabSuite/Core/Ui/Dropdown/Reusable',
-                       'Ui/Screen': 'WoltLabSuite/Core/Ui/Screen',
-                       'Ui/Scroll': 'WoltLabSuite/Core/Ui/Scroll',
-                       'Ui/SimpleDropdown': 'WoltLabSuite/Core/Ui/Dropdown/Simple',
-                       'Ui/TabMenu': 'WoltLabSuite/Core/Ui/TabMenu',
-                       'Upload': 'WoltLabSuite/Core/Upload',
-                       'User': 'WoltLabSuite/Core/User'
-               }
-       },
-       waitSeconds: 0
-});
-
-/* Define jQuery shim. We cannot use the shim object in the configuration above,
-   because it tries to load the file, even if the exported global already exists.
-   This shim is needed for jQuery plugins supporting an AMD loaded jQuery, because
-   we break the AMD support of jQuery for BC reasons.
-*/
-define('jquery', [],function() {
-       return window.jQuery;
-});
-
-
-define("require.config", function(){});
-
-/**
- * Collection of global short hand functions.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- */
-(function(window, document) {
-       /**
-        * Shorthand function to retrieve or set an attribute.
-        * 
-        * @param       {Element}       element         target element
-        * @param       {string}        attribute       attribute name
-        * @param       {?=}            value           attribute value, omit if attribute should be read
-        * @return      {(string|undefined)}            attribute value, empty string if attribute is not set or undefined if `value` was omitted
-        */
-       window.elAttr = function(element, attribute, value) {
-               if (value === undefined) {
-                       return element.getAttribute(attribute) || '';
-               }
-               
-               element.setAttribute(attribute, value);
-       };
-       
-       /**
-        * Shorthand function to retrieve a boolean attribute.
-        * 
-        * @param       {Element}       element         target element
-        * @param       {string}        attribute       attribute name
-        * @return      {boolean}       true if value is either `1` or `true`
-        */
-       window.elAttrBool = function(element, attribute) {
-               var value = elAttr(element, attribute);
-               
-               return (value === "1" || value === "true");
-       };
-       
-       /**
-        * Shorthand function to find elements by class name.
-        * 
-        * @param       {string}        className       CSS class name
-        * @param       {Element=}      context         target element, assuming `document` if omitted
-        * @return      {NodeList}      matching elements
-        */
-       window.elByClass = function(className, context) {
-               return (context || document).getElementsByClassName(className);
-       };
-       
-       /**
-        * Shorthand function to retrieve an element by id.
-        * 
-        * @param       {string}        id      element id
-        * @return      {(Element|null)}        matching element or null if not found
-        */
-       window.elById = function(id) {
-               return document.getElementById(id);
-       };
-       
-       /**
-        * Shorthand function to find an element by CSS selector.
-        * 
-        * @param       {string}        selector        CSS selector
-        * @param       {Element=}      context         target element, assuming `document` if omitted
-        * @return      {(Element|null)}                matching element or null if no match
-        */
-       window.elBySel = function(selector, context) {
-               return (context || document).querySelector(selector);
-       };
-       
-       /**
-        * Shorthand function to find elements by CSS selector.
-        * 
-        * @param       {string}        selector        CSS selector
-        * @param       {Element=}      context         target element, assuming `document` if omitted
-        * @param       {function=}     callback        callback function passed to forEach()
-        * @return      {NodeList}      matching elements
-        */
-       window.elBySelAll = function(selector, context, callback) {
-               var nodeList = (context || document).querySelectorAll(selector);
-               if (typeof callback === 'function') {
-                       Array.prototype.forEach.call(nodeList, callback);
-               }
-               
-               return nodeList;
-       };
-       
-       /**
-        * Shorthand function to find elements by tag name.
-        * 
-        * @param       {string}        tagName         element tag name
-        * @param       {Element=}      context         target element, assuming `document` if omitted
-        * @return      {NodeList}      matching elements
-        */
-       window.elByTag = function(tagName, context) {
-               return (context || document).getElementsByTagName(tagName);
-       };
-       
-       /**
-        * Shorthand function to create a DOM element.
-        * 
-        * @param       {string}        tagName         element tag name
-        * @return      {Element}       new DOM element
-        */
-       window.elCreate = function(tagName) {
-               return document.createElement(tagName);
-       };
-       
-       /**
-        * Returns the closest element (parent for text nodes), optionally matching
-        * the provided selector.
-        * 
-        * @param       {Node}          node            start node
-        * @param       {string=}       selector        optional CSS selector
-        * @return      {Element}       closest matching element
-        */
-       window.elClosest = function (node, selector) {
-               if (!(node instanceof Node)) {
-                       throw new TypeError('Provided element is not a Node.');
-               }
-               
-               // retrieve the parent element for text nodes
-               if (node.nodeType === Node.TEXT_NODE) {
-                       node = node.parentNode;
-                       
-                       // text node had no parent
-                       if (node === null) return null;
-               }
-               
-               if (typeof selector !== 'string') selector = '';
-               
-               if (selector.length === 0) return node;
-               
-               return node.closest(selector);
-       };
-       
-       /**
-        * Shorthand function to retrieve or set a 'data-' attribute.
-        * 
-        * @param       {Element}       element         target element
-        * @param       {string}        attribute       attribute name
-        * @param       {?=}            value           attribute value, omit if attribute should be read
-        * @return      {(string|undefined)}            attribute value, empty string if attribute is not set or undefined if `value` was omitted
-        */
-       window.elData = function(element, attribute, value) {
-               attribute = 'data-' + attribute;
-               
-               if (value === undefined) {
-                       return element.getAttribute(attribute) || '';
-               }
-               
-               element.setAttribute(attribute, value);
-       };
-       
-       /**
-        * Shorthand function to retrieve a boolean 'data-' attribute.
-        * 
-        * @param       {Element}       element         target element
-        * @param       {string}        attribute       attribute name
-        * @return      {boolean}       true if value is either `1` or `true`
-        */
-       window.elDataBool = function(element, attribute) {
-               var value = elData(element, attribute);
-               
-               return (value === "1" || value === "true");
-       };
-       
-       /**
-        * Shorthand function to hide an element by setting its 'display' value to 'none'.
-        * 
-        * @param       {Element}       element         DOM element
-        */
-       window.elHide = function(element) {
-               element.style.setProperty('display', 'none', '');
-       };
-       
-       /**
-        * Shorthand function to check if given element is hidden by setting its 'display'
-        * value to 'none'.
-        *
-        * @param       {Element}       element         DOM element
-        * @return      {boolean}
-        */
-       window.elIsHidden = function(element) {
-               return element.style.getPropertyValue('display') === 'none';
-       }
-       
-       /**
-        * Displays or removes an error message below the provided element.
-        * 
-        * @param       {Element}       element         DOM element
-        * @param       {string?}       errorMessage    error message; `false`, `null` and `undefined` are treated as an empty string
-        * @param       {boolean?}      isHtml          defaults to false, causes `errorMessage` to be treated as text only
-        * @return      {?Element}      the inner error element or null if it was removed
-        */
-       window.elInnerError = function (element, errorMessage, isHtml) {
-               var parent = element.parentNode;
-               if (parent === null) {
-                       throw new Error('Only elements that have a parent element or document are valid.');
-               }
-               
-               if (typeof errorMessage !== 'string') {
-                       if (errorMessage === undefined || errorMessage === null || errorMessage === false) {
-                               errorMessage = '';
-                       }
-                       else {
-                               throw new TypeError('The error message must be a string; `false`, `null` or `undefined` can be used as a substitute for an empty string.');
-                       }
-               }
-               
-               var innerError = element.nextElementSibling;
-               if (innerError === null || innerError.nodeName !== 'SMALL' || !innerError.classList.contains('innerError')) {
-                       if (errorMessage === '') {
-                               innerError = null;
-                       }
-                       else {
-                               innerError = elCreate('small');
-                               innerError.className = 'innerError';
-                               parent.insertBefore(innerError, element.nextSibling);
-                       }
-               }
-               
-               if (errorMessage === '') {
-                       if (innerError !== null) {
-                               parent.removeChild(innerError);
-                               innerError = null;
-                       }
-               }
-               else {
-                       innerError[(isHtml ? 'innerHTML' : 'textContent')] = errorMessage;
-               }
-               
-               return innerError;
-       };
-       
-       /**
-        * Shorthand function to remove an element.
-        * 
-        * @param       {Node}          element         DOM node
-        */
-       window.elRemove = function(element) {
-               element.parentNode.removeChild(element);
-       };
-       
-       /**
-        * Shorthand function to show an element previously hidden by using `elHide()`.
-        * 
-        * @param       {Element}       element         DOM element
-        */
-       window.elShow = function(element) {
-               element.style.removeProperty('display');
-       };
-       
-       /**
-        * Toggles visibility of an element using the display style.
-        * 
-        * @param       {Element}       element         DOM element
-        */
-       window.elToggle = function (element) {
-               if (element.style.getPropertyValue('display') === 'none') {
-                       elShow(element);
-               }
-               else {
-                       elHide(element);
-               }
-       };
-       
-       /**
-        * Shorthand function to iterative over an array-like object, arguments passed are the value and the index second.
-        * 
-        * Do not use this function if a simple `for()` is enough or `list` is a plain object.
-        * 
-        * @param       {object}        list            array-like object
-        * @param       {function}      callback        callback function
-        */
-       window.forEach = function(list, callback) {
-               for (var i = 0, length = list.length; i < length; i++) {
-                       callback(list[i], i);
-               }
-       };
-       
-       /**
-        * Shorthand function to check if an object has a property while ignoring the chain.
-        * 
-        * @param       {object}        obj             target object
-        * @param       {string}        property        property name
-        * @return      {boolean}       false if property does not exist or belongs to the chain
-        */
-       window.objOwns = function(obj, property) {
-               return obj.hasOwnProperty(property);
-       };
-       
-       /* assigns a global constant defining the proper 'click' event depending on the browser,
-          enforcing 'touchstart' on mobile devices for a better UX. We're using defineProperty()
-          here because at the time of writing Safari does not support 'const'. Thanks Safari.
-        */
-       var clickEvent = ('touchstart' in document.documentElement || 'ontouchstart' in window || navigator.MaxTouchPoints > 0 || navigator.msMaxTouchPoints > 0) ? 'touchstart' : 'click';
-       Object.defineProperty(window, 'WCF_CLICK_EVENT', {
-               value: 'click' //clickEvent
-       });
-       
-       /* Overwrites any history states after 'initial' with 'skip' on initial page load.
-          This is done, as the necessary DOM of other history states may not exist any more.
-          On forward navigation these 'skip' states are automatically skipped, otherwise the
-          user might have to press the forward button several times.
-          Note: A 'skip' state cannot be hit in the 'popstate' event when navigation backwards,
-                because the history already is left of all the 'skip' states for the current page.
-          Note 2: Setting the URL component of `history.replaceState()` to an empty string will
-                  cause the Internet Explorer to discard the path and query string from the
-                  address bar.
-        */
-       (function() {
-               var stateDepth = 0;
-               function check() {
-                       if (window.history.state && window.history.state.name && window.history.state.name !== 'initial') {
-                               window.history.replaceState({
-                                       name: 'skip',
-                                       depth: ++stateDepth
-                               }, '');
-                               window.history.back();
-                               
-                               // window.history does not update in this iteration of the event loop
-                               setTimeout(check, 1);
-                       }
-                       else {
-                               window.history.replaceState({name: 'initial'}, '');
-                       }
-               }
-               check();
-               
-               window.addEventListener('popstate', function(event) {
-                       if (event.state && event.state.name && event.state.name === 'skip') {
-                               window.history.go(event.state.depth);
-                       }
-               });
-       })();
-       
-       /**
-        * Provides a hashCode() method for strings, similar to Java's String.hashCode().
-        *
-        * @see http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
-        */
-       window.String.prototype.hashCode = function() {
-               var $char;
-               var $hash = 0;
-               
-               if (this.length) {
-                       for (var $i = 0, $length = this.length; $i < $length; $i++) {
-                               $char = this.charCodeAt($i);
-                               $hash = (($hash << 5) - $hash) + $char;
-                               $hash = $hash & $hash; // convert to 32bit integer
-                       }
-               }
-               
-               return $hash;
-       };
-})(window, document);
-
-define("wcf.globalHelper", function(){});
-
-/**
- * Provides the basic core functionality.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Core
- */
-define('WoltLabSuite/Core/Core',[], function() {
-       "use strict";
-       
-       var _clone = function(variable) {
-               if (typeof variable === 'object' && (Array.isArray(variable) || Core.isPlainObject(variable))) {
-                       return _cloneObject(variable);
-               }
-               
-               return variable;
-       };
-       
-       var _cloneObject = function(obj) {
-               if (!obj) {
-                       return null;
-               }
-               
-               if (Array.isArray(obj)) {
-                       return obj.slice();
-               }
-               
-               var newObj = {};
-               for (var key in obj) {
-                       if (obj.hasOwnProperty(key) && typeof obj[key] !== 'undefined') {
-                               newObj[key] = _clone(obj[key]);
-                       }
-               }
-               
-               return newObj;
-       };
-       
-       //noinspection JSUnresolvedVariable
-       var _prefix = 'wsc' + window.WCF_PATH.hashCode() + '-';
-       
-       /**
-        * @exports     WoltLabSuite/Core/Core
-        */
-       var Core = {
-               /**
-                * Deep clones an object.
-                * 
-                * @param       {object}        obj     source object
-                * @return      {object}        cloned object
-                */
-               clone: function(obj) {
-                       return _clone(obj);
-               },
-               
-               /**
-                * Converts WCF 2.0-style URLs into the default URL layout.
-                * 
-                * @param       string  url     target url
-                * @return      rewritten url
-                */
-               convertLegacyUrl: function(url) {
-                       return url.replace(/^index\.php\/(.*?)\/\?/, function(match, controller) {
-                               var parts = controller.split(/([A-Z][a-z0-9]+)/);
-                               controller = '';
-                               for (var i = 0, length = parts.length; i < length; i++) {
-                                       var part = parts[i].trim();
-                                       if (part.length) {
-                                               if (controller.length) controller += '-';
-                                               controller += part.toLowerCase();
-                                       }
-                               }
-                               
-                               return 'index.php?' + controller + '/&';
-                       });
-               },
-               
-               /**
-                * Merges objects with the first argument.
-                * 
-                * @param       {object}        out             destination object
-                * @param       {...object}     arguments       variable number of objects to be merged into the destination object
-                * @return      {object}        destination object with all provided objects merged into
-                */
-               extend: function(out) {
-                       out = out || {};
-                       var newObj = this.clone(out);
-                       
-                       for (var i = 1, length = arguments.length; i < length; i++) {
-                               var obj = arguments[i];
-                               
-                               if (!obj) continue;
-                               
-                               for (var key in obj) {
-                                       if (objOwns(obj, key)) {
-                                               if (!Array.isArray(obj[key]) && typeof obj[key] === 'object') {
-                                                       if (this.isPlainObject(obj[key])) {
-                                                               // object literals have the prototype of Object which in return has no parent prototype
-                                                               newObj[key] = this.extend(out[key], obj[key]);
-                                                       }
-                                                       else {
-                                                               newObj[key] = obj[key];
-                                                       }
-                                               }
-                                               else {
-                                                       newObj[key] = obj[key];
-                                               }
-                                       }
-                               }
-                       }
-                       
-                       return newObj;
-               },
-               
-               /**
-                * Inherits the prototype methods from one constructor to another
-                * constructor.
-                * 
-                * Usage:
-                * 
-                * function MyDerivedClass() {}
-                * Core.inherit(MyDerivedClass, TheAwesomeBaseClass, {
-                *      // regular prototype for `MyDerivedClass`
-                *      
-                *      overwrittenMethodFromBaseClass: function(foo, bar) {
-                *              // do stuff
-                *              
-                *              // invoke parent
-                *              MyDerivedClass._super.prototype.overwrittenMethodFromBaseClass.call(this, foo, bar);
-                *      }
-                * });
-                * 
-                * @see https://github.com/nodejs/node/blob/7d14dd9b5e78faabb95d454a79faa513d0bbc2a5/lib/util.js#L697-L735
-                * @param       {function}      constructor             inheriting constructor function
-                * @param       {function}      superConstructor        inherited constructor function
-                * @param       {object=}       propertiesObject        additional prototype properties
-                */
-               inherit: function(constructor, superConstructor, propertiesObject) {
-                       if (constructor === undefined || constructor === null) {
-                               throw new TypeError("The constructor must not be undefined or null.");
-                       }
-                       if (superConstructor === undefined || superConstructor === null) {
-                               throw new TypeError("The super constructor must not be undefined or null.");
-                       }
-                       if (superConstructor.prototype === undefined) {
-                               throw new TypeError("The super constructor must have a prototype.");
-                       }
-                       
-                       constructor._super = superConstructor;
-                       constructor.prototype = Core.extend(Object.create(superConstructor.prototype, {
-                               constructor: {
-                                       configurable: true,
-                                       enumerable: false,
-                                       value: constructor,
-                                       writable: true
-                               }
-                       }), propertiesObject || {});
-               },
-               
-               /**
-                * Returns true if `obj` is an object literal.
-                * 
-                * @param       {*}     obj     target object
-                * @returns     {boolean}       true if target is an object literal
-                */
-               isPlainObject: function(obj) {
-                       if (typeof obj !== 'object' || obj === null || obj.nodeType) {
-                               return false;
-                       }
-                       
-                       return (Object.getPrototypeOf(obj) === Object.prototype);
-               },
-               
-               /**
-                * Returns the object's class name.
-                * 
-                * @param       {object}        obj     target object
-                * @return      {string}        object class name
-                */
-               getType: function(obj) {
-                       return Object.prototype.toString.call(obj).replace(/^\[object (.+)\]$/, '$1');
-               },
-               
-               /**
-                * Returns a RFC4122 version 4 compilant UUID.
-                * 
-                * @see         http://stackoverflow.com/a/2117523
-                * @return      {string}
-                */
-               getUuid: function() {
-                       return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
-                               var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
-                               return v.toString(16);
-                       });
-               },
-               
-               /**
-                * Recursively serializes an object into an encoded URI parameter string.
-                *  
-                * @param       {object}        obj     target object
-                * @param       {string=}       prefix  parameter prefix
-                * @return      {string}        encoded parameter string
-                */
-               serialize: function(obj, prefix) {
-                       var parameters = [];
-                       
-                       for (var key in obj) {
-                               if (objOwns(obj, key)) {
-                                       var parameterKey = (prefix) ? prefix + '[' + key + ']' : key;
-                                       var value = obj[key];
-                                       
-                                       if (typeof value === 'object') {
-                                               parameters.push(this.serialize(value, parameterKey));
-                                       }
-                                       else {
-                                               parameters.push(encodeURIComponent(parameterKey) + '=' + encodeURIComponent(value));
-                                       }
-                               }
-                       }
-                       
-                       return parameters.join('&');
-               },
-               
-               /**
-                * Triggers a custom or built-in event.
-                * 
-                * @param       {Element}       element         target element
-                * @param       {string}        eventName       event name
-                */
-               triggerEvent: function(element, eventName) {
-                       var event;
-                       
-                       try {
-                               event = new Event(eventName, {
-                                       bubbles: true,
-                                       cancelable: true
-                               });
-                       }
-                       catch (e) {
-                               event = document.createEvent('Event');
-                               event.initEvent(eventName, true, true);
-                       }
-                       
-                       element.dispatchEvent(event);
-               },
-               
-               /**
-                * Returns the unique prefix for the localStorage.
-                * 
-                * @return      {string}        prefix for the localStorage
-                */
-               getStoragePrefix: function() {
-                       return _prefix;
-               }
-       };
-       
-       return Core;
-});
-
-/**
- * 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`.
- * 
- * @author     Tim Duesterhus, Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Dictionary
- */
-define('WoltLabSuite/Core/Dictionary',['Core'], function(Core) {
-       "use strict";
-       
-       var _hasMap = objOwns(window, 'Map') && typeof window.Map === 'function';
-       
-       /**
-        * @constructor
-        */
-       function Dictionary() {
-               this._dictionary = (_hasMap) ? new Map() : {};
-       }
-       Dictionary.prototype = {
-               /**
-                * Sets a new key with given value, will overwrite an existing key.
-                * 
-                * @param       {(number|string)}       key     key
-                * @param       {?}                     value   value
-                */
-               set: function(key, value) {
-                       if (typeof key === 'number') key = key.toString();
-                       
-                       if (typeof key !== "string") {
-                               throw new TypeError("Only strings can be used as keys, rejected '" + key + "' (" + typeof key + ").");
-                       }
-                       
-                       if (_hasMap) this._dictionary.set(key, value);
-                       else this._dictionary[key] = value;
-               },
-               
-               /**
-                * Removes a key from the dictionary.
-                * 
-                * @param       {(number|string)}       key     key
-                */
-               'delete': function(key) {
-                       if (typeof key === 'number') key = key.toString();
-                       
-                       if (_hasMap) this._dictionary['delete'](key);
-                       else this._dictionary[key] = undefined;
-               },
-               
-               /**
-                * Returns true if dictionary contains a value for given key and is not undefined.
-                * 
-                * @param       {(number|string)}       key     key
-                * @return      {boolean}       true if key exists and value is not undefined
-                */
-               has: function(key) {
-                       if (typeof key === 'number') key = key.toString();
-                       
-                       if (_hasMap) return this._dictionary.has(key);
-                       else {
-                               return (objOwns(this._dictionary, key) && typeof this._dictionary[key] !== "undefined");
-                       }
-               },
-               
-               /**
-                * Retrieves a value by key, returns undefined if there is no match.
-                * 
-                * @param       {(number|string)}       key     key
-                * @return      {*}
-                */
-               get: function(key) {
-                       if (typeof key === 'number') key = key.toString();
-                       
-                       if (this.has(key)) {
-                               if (_hasMap) return this._dictionary.get(key);
-                               else return this._dictionary[key];
-                       }
-                       
-                       return undefined;
-               },
-               
-               /**
-                * Iterates over the dictionary keys and values, callback function should expect the
-                * value as first parameter and the key name second.
-                * 
-                * @param       {function<*, string>}   callback        callback for each iteration
-                */
-               forEach: function(callback) {
-                       if (typeof callback !== "function") {
-                               throw new TypeError("forEach() expects a callback as first parameter.");
-                       }
-                       
-                       if (_hasMap) {
-                               this._dictionary.forEach(callback);
-                       }
-                       else {
-                               var keys = Object.keys(this._dictionary);
-                               for (var i = 0, length = keys.length; i < length; i++) {
-                                       callback(this._dictionary[keys[i]], keys[i]);
-                               }
-                       }
-               },
-               
-               /**
-                * Merges one or more Dictionary instances into this one.
-                * 
-                * @param       {...Dictionary}         var_args        one or more Dictionary instances
-                */
-               merge: function() {
-                       for (var i = 0, length = arguments.length; i < length; i++) {
-                               var dictionary = arguments[i];
-                               if (!(dictionary instanceof Dictionary)) {
-                                       throw new TypeError("Expected an object of type Dictionary, but argument " + i + " is not.");
-                               }
-                               
-                               dictionary.forEach((function(value, key) {
-                                       this.set(key, value);
-                               }).bind(this));
-                       }
-               },
-               
-               /**
-                * Returns the object representation of the dictionary.
-                * 
-                * @return      {object}        dictionary's object representation
-                */
-               toObject: function() {
-                       if (!_hasMap) return Core.clone(this._dictionary);
-                       
-                       var object = { };
-                       this._dictionary.forEach(function(value, key) {
-                               object[key] = value;
-                       });
-                       
-                       return object;
-               }
-       };
-       
-       /**
-        * Creates a new Dictionary based on the given object.
-        * All properties that are owned by the object will be added
-        * as keys to the resulting Dictionary.
-        * 
-        * @param       {object}        object
-        * @return      {Dictionary}
-        */
-       Dictionary.fromObject = function(object) {
-               var result = new Dictionary();
-               
-               for (var key in object) {
-                       if (objOwns(object, key)) {
-                               result.set(key, object[key]);
-                       }
-               }
-               
-               return result;
-       };
-       
-       Object.defineProperty(Dictionary.prototype, 'size', {
-               enumerable: false,
-               configurable: true,
-               get: function() {
-                       if (_hasMap) {
-                               return this._dictionary.size;
-                       }
-                       else {
-                               return Object.keys(this._dictionary).length;
-                       }
-               }
-       });
-       
-       return Dictionary;
-});
-
-
-
-define('WoltLabSuite/Core/Template.grammar',['require'],function(require){
-var o=function(k,v,o,l){for(o=o||{},l=k.length;l--;o[k[l]]=v);return o},$V0=[2,37],$V1=[5,9,11,12,13,18,19,21,22,23,25,26,27,28,30,31,32,33,35,37,39],$V2=[1,24],$V3=[1,25],$V4=[1,31],$V5=[1,29],$V6=[1,30],$V7=[1,26],$V8=[1,27],$V9=[1,33],$Va=[11,12,15,40,41,45,47,49,50,52],$Vb=[9,11,12,13,18,19,21,23,26,28,30,31,32,33,35,37],$Vc=[11,12,15,40,41,44,45,46,47,49,50,52],$Vd=[18,35,37],$Ve=[12,15];
-var parser = {trace: function trace() { },
-yy: {},
-symbols_: {"error":2,"TEMPLATE":3,"CHUNK_STAR":4,"EOF":5,"CHUNK_STAR_repetition0":6,"CHUNK":7,"PLAIN_ANY":8,"T_LITERAL":9,"COMMAND":10,"T_ANY":11,"T_WS":12,"{if":13,"COMMAND_PARAMETERS":14,"}":15,"COMMAND_repetition0":16,"COMMAND_option0":17,"{/if}":18,"{include":19,"COMMAND_PARAMETER_LIST":20,"{implode":21,"{/implode}":22,"{foreach":23,"COMMAND_option1":24,"{/foreach}":25,"{lang}":26,"{/lang}":27,"{":28,"VARIABLE":29,"{#":30,"{@":31,"{ldelim}":32,"{rdelim}":33,"ELSE":34,"{else}":35,"ELSE_IF":36,"{elseif":37,"FOREACH_ELSE":38,"{foreachelse}":39,"T_VARIABLE":40,"T_VARIABLE_NAME":41,"VARIABLE_repetition0":42,"VARIABLE_SUFFIX":43,"[":44,"]":45,".":46,"(":47,"VARIABLE_SUFFIX_option0":48,")":49,"=":50,"COMMAND_PARAMETER_VALUE":51,"T_QUOTED_STRING":52,"COMMAND_PARAMETERS_repetition_plus0":53,"COMMAND_PARAMETER":54,"$accept":0,"$end":1},
-terminals_: {2:"error",5:"EOF",9:"T_LITERAL",11:"T_ANY",12:"T_WS",13:"{if",15:"}",18:"{/if}",19:"{include",21:"{implode",22:"{/implode}",23:"{foreach",25:"{/foreach}",26:"{lang}",27:"{/lang}",28:"{",30:"{#",31:"{@",32:"{ldelim}",33:"{rdelim}",35:"{else}",37:"{elseif",39:"{foreachelse}",40:"T_VARIABLE",41:"T_VARIABLE_NAME",44:"[",45:"]",46:".",47:"(",49:")",50:"=",52:"T_QUOTED_STRING"},
-productions_: [0,[3,2],[4,1],[7,1],[7,1],[7,1],[8,1],[8,1],[10,7],[10,3],[10,5],[10,6],[10,3],[10,3],[10,3],[10,3],[10,1],[10,1],[34,2],[36,4],[38,2],[29,3],[43,3],[43,2],[43,3],[20,5],[20,3],[51,1],[51,1],[14,1],[54,1],[54,1],[54,1],[54,1],[54,1],[54,1],[54,3],[6,0],[6,2],[16,0],[16,2],[17,0],[17,1],[24,0],[24,1],[42,0],[42,2],[48,0],[48,1],[53,1],[53,2]],
-performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) {
-/* this == yyval */
-
-var $0 = $$.length - 1;
-switch (yystate) {
-case 1:
- return $$[$0-1] + ";"; 
-break;
-case 2:
-
-       var result = $$[$0].reduce(function (carry, item) {
-               if (item.encode && !carry[1]) carry[0] += " + '" + item.value;
-               else if (item.encode && carry[1]) carry[0] += item.value;
-               else if (!item.encode && carry[1]) carry[0] += "' + " + item.value;
-               else if (!item.encode && !carry[1]) carry[0] += " + " + item.value;
-               
-               carry[1] = item.encode;
-               return carry;
-       }, [ "''", false ]);
-       if (result[1]) result[0] += "'";
-       
-       this.$ = result[0];
-
-break;
-case 3: case 4:
-this.$ = { encode: true, value: $$[$0].replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/(\r\n|\n|\r)/g, '\\n') };
-break;
-case 5:
-this.$ = { encode: false, value: $$[$0] };
-break;
-case 8:
-
-               this.$ = "(function() { if (" + $$[$0-5] + ") { return " + $$[$0-3] + "; } " + $$[$0-2].join(' ') + " " + ($$[$0-1] || '') + " return ''; })()";
-       
-break;
-case 9:
-
-               if (!$$[$0-1]['file']) throw new Error('Missing parameter file');
-               
-               this.$ = $$[$0-1]['file'] + ".fetch(v)";
-       
-break;
-case 10:
-
-               if (!$$[$0-3]['from']) throw new Error('Missing parameter from');
-               if (!$$[$0-3]['item']) throw new Error('Missing parameter item');
-               if (!$$[$0-3]['glue']) $$[$0-3]['glue'] = "', '";
-               
-               this.$ = "(function() { return " + $$[$0-3]['from'] + ".map(function(item) { v[" + $$[$0-3]['item'] + "] = item; return " + $$[$0-1] + "; }).join(" + $$[$0-3]['glue'] + "); })()";
-       
-break;
-case 11:
-
-               if (!$$[$0-4]['from']) throw new Error('Missing parameter from');
-               if (!$$[$0-4]['item']) throw new Error('Missing parameter item');
-               
-               this.$ = "(function() {"
-               + "var looped = false, result = '';"
-               + "if (" + $$[$0-4]['from'] + " instanceof Array) {"
-                       + "for (var i = 0; i < " + $$[$0-4]['from'] + ".length; i++) { looped = true;"
-                               + "v[" + $$[$0-4]['key'] + "] = i;"
-                               + "v[" + $$[$0-4]['item'] + "] = " + $$[$0-4]['from'] + "[i];"
-                               + "result += " + $$[$0-2] + ";"
-                       + "}"
-               + "} else {"
-                       + "for (var key in " + $$[$0-4]['from'] + ") {"
-                               + "if (!" + $$[$0-4]['from'] + ".hasOwnProperty(key)) continue;"
-                               + "looped = true;"
-                               + "v[" + $$[$0-4]['key'] + "] = key;"
-                               + "v[" + $$[$0-4]['item'] + "] = " + $$[$0-4]['from'] + "[key];"
-                               + "result += " + $$[$0-2] + ";"
-                       + "}"
-               + "}"
-               + "return (looped ? result : " + ($$[$0-1] || "''") + "); })()"
-       
-break;
-case 12:
-this.$ = "Language.get(" + $$[$0-1] + ", v)";
-break;
-case 13:
-this.$ = "StringUtil.escapeHTML(" + $$[$0-1] + ")";
-break;
-case 14:
-this.$ = "StringUtil.formatNumeric(" + $$[$0-1] + ")";
-break;
-case 15:
-this.$ = $$[$0-1];
-break;
-case 16:
-this.$ = "'{'";
-break;
-case 17:
-this.$ = "'}'";
-break;
-case 18:
-this.$ = "else { return " + $$[$0] + "; }";
-break;
-case 19:
-this.$ = "else if (" + $$[$0-2] + ") { return " + $$[$0] + "; }";
-break;
-case 20:
-this.$ = $$[$0];
-break;
-case 21:
-this.$ = "v['" + $$[$0-1] + "']" + $$[$0].join('');;
-break;
-case 22:
-this.$ = $$[$0-2] + $$[$0-1] + $$[$0];
-break;
-case 23:
-this.$ = "['" + $$[$0] + "']";
-break;
-case 24: case 36:
-this.$ = $$[$0-2] + ($$[$0-1] || '') + $$[$0];
-break;
-case 25:
- this.$ = $$[$0]; this.$[$$[$0-4]] = $$[$0-2]; 
-break;
-case 26:
- this.$ = {}; this.$[$$[$0-2]] = $$[$0]; 
-break;
-case 29:
-this.$ = $$[$0].join('');
-break;
-case 37: case 39: case 45:
-this.$ = [];
-break;
-case 38: case 40: case 46: case 50:
-$$[$0-1].push($$[$0]);
-break;
-case 49:
-this.$ = [$$[$0]];
-break;
-}
-},
-table: [o([5,9,11,12,13,19,21,23,26,28,30,31,32,33],$V0,{3:1,4:2,6:3}),{1:[3]},{5:[1,4]},o([5,18,22,25,27,35,37,39],[2,2],{7:5,8:6,10:8,9:[1,7],11:[1,9],12:[1,10],13:[1,11],19:[1,12],21:[1,13],23:[1,14],26:[1,15],28:[1,16],30:[1,17],31:[1,18],32:[1,19],33:[1,20]}),{1:[2,1]},o($V1,[2,38]),o($V1,[2,3]),o($V1,[2,4]),o($V1,[2,5]),o($V1,[2,6]),o($V1,[2,7]),{11:$V2,12:$V3,14:21,29:28,40:$V4,41:$V5,47:$V6,50:$V7,52:$V8,53:22,54:23},{20:32,41:$V9},{20:34,41:$V9},{20:35,41:$V9},o([9,11,12,13,19,21,23,26,27,28,30,31,32,33],$V0,{6:3,4:36}),{29:37,40:$V4},{29:38,40:$V4},{29:39,40:$V4},o($V1,[2,16]),o($V1,[2,17]),{15:[1,40]},o([15,45,49],[2,29],{29:28,54:41,11:$V2,12:$V3,40:$V4,41:$V5,47:$V6,50:$V7,52:$V8}),o($Va,[2,49]),o($Va,[2,30]),o($Va,[2,31]),o($Va,[2,32]),o($Va,[2,33]),o($Va,[2,34]),o($Va,[2,35]),{11:$V2,12:$V3,14:42,29:28,40:$V4,41:$V5,47:$V6,50:$V7,52:$V8,53:22,54:23},{41:[1,43]},{15:[1,44]},{50:[1,45]},{15:[1,46]},{15:[1,47]},{27:[1,48]},{15:[1,49]},{15:[1,50]},{15:[1,51]},o($Vb,$V0,{6:3,4:52}),o($Va,[2,50]),{49:[1,53]},o($Vc,[2,45],{42:54}),o($V1,[2,9]),{29:57,40:$V4,51:55,52:[1,56]},o([9,11,12,13,19,21,22,23,26,28,30,31,32,33],$V0,{6:3,4:58}),o([9,11,12,13,19,21,23,25,26,28,30,31,32,33,39],$V0,{6:3,4:59}),o($V1,[2,12]),o($V1,[2,13]),o($V1,[2,14]),o($V1,[2,15]),o($Vd,[2,39],{16:60}),o($Va,[2,36]),o([11,12,15,40,41,45,49,50,52],[2,21],{43:61,44:[1,62],46:[1,63],47:[1,64]}),{12:[1,65],15:[2,26]},o($Ve,[2,27]),o($Ve,[2,28]),{22:[1,66]},{24:67,25:[2,43],38:68,39:[1,69]},{17:70,18:[2,41],34:72,35:[1,74],36:71,37:[1,73]},o($Vc,[2,46]),{11:$V2,12:$V3,14:75,29:28,40:$V4,41:$V5,47:$V6,50:$V7,52:$V8,53:22,54:23},{41:[1,76]},{11:$V2,12:$V3,14:78,29:28,40:$V4,41:$V5,47:$V6,48:77,49:[2,47],50:$V7,52:$V8,53:22,54:23},{20:79,41:$V9},o($V1,[2,10]),{25:[1,80]},{25:[2,44]},o([9,11,12,13,19,21,23,25,26,28,30,31,32,33],$V0,{6:3,4:81}),{18:[1,82]},o($Vd,[2,40]),{18:[2,42]},{11:$V2,12:$V3,14:83,29:28,40:$V4,41:$V5,47:$V6,50:$V7,52:$V8,53:22,54:23},o([9,11,12,13,18,19,21,23,26,28,30,31,32,33],$V0,{6:3,4:84}),{45:[1,85]},o($Vc,[2,23]),{49:[1,86]},{49:[2,48]},{15:[2,25]},o($V1,[2,11]),{25:[2,20]},o($V1,[2,8]),{15:[1,87]},{18:[2,18]},o($Vc,[2,22]),o($Vc,[2,24]),o($Vb,$V0,{6:3,4:88}),o($Vd,[2,19])],
-defaultActions: {4:[2,1],68:[2,44],72:[2,42],78:[2,48],79:[2,25],81:[2,20],84:[2,18]},
-parseError: function parseError(str, hash) {
-    if (hash.recoverable) {
-        this.trace(str);
-    } else {
-        function _parseError (msg, hash) {
-            this.message = msg;
-            this.hash = hash;
-        }
-        _parseError.prototype = Error;
-
-        throw new _parseError(str, hash);
-    }
-},
-parse: function parse(input) {
-    var self = this, stack = [0], tstack = [], vstack = [null], lstack = [], table = this.table, yytext = '', yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1;
-    var args = lstack.slice.call(arguments, 1);
-    var lexer = Object.create(this.lexer);
-    var sharedState = { yy: {} };
-    for (var k in this.yy) {
-        if (Object.prototype.hasOwnProperty.call(this.yy, k)) {
-            sharedState.yy[k] = this.yy[k];
-        }
-    }
-    lexer.setInput(input, sharedState.yy);
-    sharedState.yy.lexer = lexer;
-    sharedState.yy.parser = this;
-    if (typeof lexer.yylloc == 'undefined') {
-        lexer.yylloc = {};
-    }
-    var yyloc = lexer.yylloc;
-    lstack.push(yyloc);
-    var ranges = lexer.options && lexer.options.ranges;
-    if (typeof sharedState.yy.parseError === 'function') {
-        this.parseError = sharedState.yy.parseError;
-    } else {
-        this.parseError = Object.getPrototypeOf(this).parseError;
-    }
-    function popStack(n) {
-        stack.length = stack.length - 2 * n;
-        vstack.length = vstack.length - n;
-        lstack.length = lstack.length - n;
-    }
-    _token_stack:
-        var lex = function () {
-            var token;
-            token = lexer.lex() || EOF;
-            if (typeof token !== 'number') {
-                token = self.symbols_[token] || token;
-            }
-            return token;
-        };
-    var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected;
-    while (true) {
-        state = stack[stack.length - 1];
-        if (this.defaultActions[state]) {
-            action = this.defaultActions[state];
-        } else {
-            if (symbol === null || typeof symbol == 'undefined') {
-                symbol = lex();
-            }
-            action = table[state] && table[state][symbol];
-        }
-                    if (typeof action === 'undefined' || !action.length || !action[0]) {
-                var errStr = '';
-                expected = [];
-                for (p in table[state]) {
-                    if (this.terminals_[p] && p > TERROR) {
-                        expected.push('\'' + this.terminals_[p] + '\'');
-                    }
-                }
-                if (lexer.showPosition) {
-                    errStr = 'Parse error on line ' + (yylineno + 1) + ':\n' + lexer.showPosition() + '\nExpecting ' + expected.join(', ') + ', got \'' + (this.terminals_[symbol] || symbol) + '\'';
-                } else {
-                    errStr = 'Parse error on line ' + (yylineno + 1) + ': Unexpected ' + (symbol == EOF ? 'end of input' : '\'' + (this.terminals_[symbol] || symbol) + '\'');
-                }
-                this.parseError(errStr, {
-                    text: lexer.match,
-                    token: this.terminals_[symbol] || symbol,
-                    line: lexer.yylineno,
-                    loc: yyloc,
-                    expected: expected
-                });
-            }
-        if (action[0] instanceof Array && action.length > 1) {
-            throw new Error('Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol);
-        }
-        switch (action[0]) {
-        case 1:
-            stack.push(symbol);
-            vstack.push(lexer.yytext);
-            lstack.push(lexer.yylloc);
-            stack.push(action[1]);
-            symbol = null;
-            if (!preErrorSymbol) {
-                yyleng = lexer.yyleng;
-                yytext = lexer.yytext;
-                yylineno = lexer.yylineno;
-                yyloc = lexer.yylloc;
-                if (recovering > 0) {
-                    recovering--;
-                }
-            } else {
-                symbol = preErrorSymbol;
-                preErrorSymbol = null;
-            }
-            break;
-        case 2:
-            len = this.productions_[action[1]][1];
-            yyval.$ = vstack[vstack.length - len];
-            yyval._$ = {
-                first_line: lstack[lstack.length - (len || 1)].first_line,
-                last_line: lstack[lstack.length - 1].last_line,
-                first_column: lstack[lstack.length - (len || 1)].first_column,
-                last_column: lstack[lstack.length - 1].last_column
-            };
-            if (ranges) {
-                yyval._$.range = [
-                    lstack[lstack.length - (len || 1)].range[0],
-                    lstack[lstack.length - 1].range[1]
-                ];
-            }
-            r = this.performAction.apply(yyval, [
-                yytext,
-                yyleng,
-                yylineno,
-                sharedState.yy,
-                action[1],
-                vstack,
-                lstack
-            ].concat(args));
-            if (typeof r !== 'undefined') {
-                return r;
-            }
-            if (len) {
-                stack = stack.slice(0, -1 * len * 2);
-                vstack = vstack.slice(0, -1 * len);
-                lstack = lstack.slice(0, -1 * len);
-            }
-            stack.push(this.productions_[action[1]][0]);
-            vstack.push(yyval.$);
-            lstack.push(yyval._$);
-            newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
-            stack.push(newState);
-            break;
-        case 3:
-            return true;
-        }
-    }
-    return true;
-}};
-
-/* generated by jison-lex 0.3.4 */
-var lexer = (function(){
-var lexer = ({
-
-EOF:1,
-
-parseError:function parseError(str, hash) {
-        if (this.yy.parser) {
-            this.yy.parser.parseError(str, hash);
-        } else {
-            throw new Error(str);
-        }
-    },
-
-// resets the lexer, sets new input
-setInput:function (input, yy) {
-        this.yy = yy || this.yy || {};
-        this._input = input;
-        this._more = this._backtrack = this.done = false;
-        this.yylineno = this.yyleng = 0;
-        this.yytext = this.matched = this.match = '';
-        this.conditionStack = ['INITIAL'];
-        this.yylloc = {
-            first_line: 1,
-            first_column: 0,
-            last_line: 1,
-            last_column: 0
-        };
-        if (this.options.ranges) {
-            this.yylloc.range = [0,0];
-        }
-        this.offset = 0;
-        return this;
-    },
-
-// consumes and returns one char from the input
-input:function () {
-        var ch = this._input[0];
-        this.yytext += ch;
-        this.yyleng++;
-        this.offset++;
-        this.match += ch;
-        this.matched += ch;
-        var lines = ch.match(/(?:\r\n?|\n).*/g);
-        if (lines) {
-            this.yylineno++;
-            this.yylloc.last_line++;
-        } else {
-            this.yylloc.last_column++;
-        }
-        if (this.options.ranges) {
-            this.yylloc.range[1]++;
-        }
-
-        this._input = this._input.slice(1);
-        return ch;
-    },
-
-// unshifts one char (or a string) into the input
-unput:function (ch) {
-        var len = ch.length;
-        var lines = ch.split(/(?:\r\n?|\n)/g);
-
-        this._input = ch + this._input;
-        this.yytext = this.yytext.substr(0, this.yytext.length - len);
-        //this.yyleng -= len;
-        this.offset -= len;
-        var oldLines = this.match.split(/(?:\r\n?|\n)/g);
-        this.match = this.match.substr(0, this.match.length - 1);
-        this.matched = this.matched.substr(0, this.matched.length - 1);
-
-        if (lines.length - 1) {
-            this.yylineno -= lines.length - 1;
-        }
-        var r = this.yylloc.range;
-
-        this.yylloc = {
-            first_line: this.yylloc.first_line,
-            last_line: this.yylineno + 1,
-            first_column: this.yylloc.first_column,
-            last_column: lines ?
-                (lines.length === oldLines.length ? this.yylloc.first_column : 0)
-                 + oldLines[oldLines.length - lines.length].length - lines[0].length :
-              this.yylloc.first_column - len
-        };
-
-        if (this.options.ranges) {
-            this.yylloc.range = [r[0], r[0] + this.yyleng - len];
-        }
-        this.yyleng = this.yytext.length;
-        return this;
-    },
-
-// When called from action, caches matched text and appends it on next action
-more:function () {
-        this._more = true;
-        return this;
-    },
-
-// When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead.
-reject:function () {
-        if (this.options.backtrack_lexer) {
-            this._backtrack = true;
-        } else {
-            return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n' + this.showPosition(), {
-                text: "",
-                token: null,
-                line: this.yylineno
-            });
-
-        }
-        return this;
-    },
-
-// retain first n characters of the match
-less:function (n) {
-        this.unput(this.match.slice(n));
-    },
-
-// displays already matched input, i.e. for error messages
-pastInput:function () {
-        var past = this.matched.substr(0, this.matched.length - this.match.length);
-        return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
-    },
-
-// displays upcoming input, i.e. for error messages
-upcomingInput:function () {
-        var next = this.match;
-        if (next.length < 20) {
-            next += this._input.substr(0, 20-next.length);
-        }
-        return (next.substr(0,20) + (next.length > 20 ? '...' : '')).replace(/\n/g, "");
-    },
-
-// displays the character position where the lexing error occurred, i.e. for error messages
-showPosition:function () {
-        var pre = this.pastInput();
-        var c = new Array(pre.length + 1).join("-");
-        return pre + this.upcomingInput() + "\n" + c + "^";
-    },
-
-// test the lexed token: return FALSE when not a match, otherwise return token
-test_match:function (match, indexed_rule) {
-        var token,
-            lines,
-            backup;
-
-        if (this.options.backtrack_lexer) {
-            // save context
-            backup = {
-                yylineno: this.yylineno,
-                yylloc: {
-                    first_line: this.yylloc.first_line,
-                    last_line: this.last_line,
-                    first_column: this.yylloc.first_column,
-                    last_column: this.yylloc.last_column
-                },
-                yytext: this.yytext,
-                match: this.match,
-                matches: this.matches,
-                matched: this.matched,
-                yyleng: this.yyleng,
-                offset: this.offset,
-                _more: this._more,
-                _input: this._input,
-                yy: this.yy,
-                conditionStack: this.conditionStack.slice(0),
-                done: this.done
-            };
-            if (this.options.ranges) {
-                backup.yylloc.range = this.yylloc.range.slice(0);
-            }
-        }
-
-        lines = match[0].match(/(?:\r\n?|\n).*/g);
-        if (lines) {
-            this.yylineno += lines.length;
-        }
-        this.yylloc = {
-            first_line: this.yylloc.last_line,
-            last_line: this.yylineno + 1,
-            first_column: this.yylloc.last_column,
-            last_column: lines ?
-                         lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length :
-                         this.yylloc.last_column + match[0].length
-        };
-        this.yytext += match[0];
-        this.match += match[0];
-        this.matches = match;
-        this.yyleng = this.yytext.length;
-        if (this.options.ranges) {
-            this.yylloc.range = [this.offset, this.offset += this.yyleng];
-        }
-        this._more = false;
-        this._backtrack = false;
-        this._input = this._input.slice(match[0].length);
-        this.matched += match[0];
-        token = this.performAction.call(this, this.yy, this, indexed_rule, this.conditionStack[this.conditionStack.length - 1]);
-        if (this.done && this._input) {
-            this.done = false;
-        }
-        if (token) {
-            return token;
-        } else if (this._backtrack) {
-            // recover context
-            for (var k in backup) {
-                this[k] = backup[k];
-            }
-            return false; // rule action called reject() implying the next rule should be tested instead.
-        }
-        return false;
-    },
-
-// return next match in input
-next:function () {
-        if (this.done) {
-            return this.EOF;
-        }
-        if (!this._input) {
-            this.done = true;
-        }
-
-        var token,
-            match,
-            tempMatch,
-            index;
-        if (!this._more) {
-            this.yytext = '';
-            this.match = '';
-        }
-        var rules = this._currentRules();
-        for (var i = 0; i < rules.length; i++) {
-            tempMatch = this._input.match(this.rules[rules[i]]);
-            if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
-                match = tempMatch;
-                index = i;
-                if (this.options.backtrack_lexer) {
-                    token = this.test_match(tempMatch, rules[i]);
-                    if (token !== false) {
-                        return token;
-                    } else if (this._backtrack) {
-                        match = false;
-                        continue; // rule action called reject() implying a rule MISmatch.
-                    } else {
-                        // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
-                        return false;
-                    }
-                } else if (!this.options.flex) {
-                    break;
-                }
-            }
-        }
-        if (match) {
-            token = this.test_match(match, rules[index]);
-            if (token !== false) {
-                return token;
-            }
-            // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
-            return false;
-        }
-        if (this._input === "") {
-            return this.EOF;
-        } else {
-            return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\n' + this.showPosition(), {
-                text: "",
-                token: null,
-                line: this.yylineno
-            });
-        }
-    },
-
-// return next match that has a token
-lex:function lex() {
-        var r = this.next();
-        if (r) {
-            return r;
-        } else {
-            return this.lex();
-        }
-    },
-
-// activates a new lexer condition state (pushes the new lexer condition state onto the condition stack)
-begin:function begin(condition) {
-        this.conditionStack.push(condition);
-    },
-
-// pop the previously active lexer condition state off the condition stack
-popState:function popState() {
-        var n = this.conditionStack.length - 1;
-        if (n > 0) {
-            return this.conditionStack.pop();
-        } else {
-            return this.conditionStack[0];
-        }
-    },
-
-// produce the lexer rule set which is active for the currently active lexer condition state
-_currentRules:function _currentRules() {
-        if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) {
-            return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules;
-        } else {
-            return this.conditions["INITIAL"].rules;
-        }
-    },
-
-// return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available
-topState:function topState(n) {
-        n = this.conditionStack.length - 1 - Math.abs(n || 0);
-        if (n >= 0) {
-            return this.conditionStack[n];
-        } else {
-            return "INITIAL";
-        }
-    },
-
-// alias for begin(condition)
-pushState:function pushState(condition) {
-        this.begin(condition);
-    },
-
-// return the number of states currently on the stack
-stateStackSize:function stateStackSize() {
-        return this.conditionStack.length;
-    },
-options: {},
-performAction: function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
-var YYSTATE=YY_START;
-switch($avoiding_name_collisions) {
-case 0:/* comment */
-break;
-case 1: yy_.yytext = yy_.yytext.substring(9, yy_.yytext.length - 10); return 9; 
-break;
-case 2:return 52;
-break;
-case 3:return 52;
-break;
-case 4:return 40;
-break;
-case 5: return 41; 
-break;
-case 6:return 46;
-break;
-case 7:return 44;
-break;
-case 8:return 45;
-break;
-case 9:return 47;
-break;
-case 10:return 49;
-break;
-case 11:return 50;
-break;
-case 12:return 32;
-break;
-case 13:return 33;
-break;
-case 14: this.begin('command'); return 30; 
-break;
-case 15: this.begin('command'); return 31; 
-break;
-case 16: this.begin('command'); return 13; 
-break;
-case 17: this.begin('command'); return 37; 
-break;
-case 18: this.begin('command'); return 37; 
-break;
-case 19:return 35;
-break;
-case 20:return 18;
-break;
-case 21:return 26;
-break;
-case 22:return 27;
-break;
-case 23: this.begin('command'); return 19; 
-break;
-case 24: this.begin('command'); return 21; 
-break;
-case 25:return 22;
-break;
-case 26: this.begin('command'); return 23; 
-break;
-case 27:return 39;
-break;
-case 28:return 25;
-break;
-case 29: this.begin('command'); return 28; 
-break;
-case 30: this.popState(); return 15;
-break;
-case 31:return 12;
-break;
-case 32:return 5;
-break;
-case 33:return 11;
-break;
-}
-},
-rules: [/^(?:\{\*[\s\S]*?\*\})/,/^(?:\{literal\}[\s\S]*?\{\/literal\})/,/^(?:"([^"]|\\\.)*")/,/^(?:'([^']|\\\.)*')/,/^(?:\$)/,/^(?:[_a-zA-Z][_a-zA-Z0-9]*)/,/^(?:\.)/,/^(?:\[)/,/^(?:\])/,/^(?:\()/,/^(?:\))/,/^(?:=)/,/^(?:\{ldelim\})/,/^(?:\{rdelim\})/,/^(?:\{#)/,/^(?:\{@)/,/^(?:\{if )/,/^(?:\{else if )/,/^(?:\{elseif )/,/^(?:\{else\})/,/^(?:\{\/if\})/,/^(?:\{lang\})/,/^(?:\{\/lang\})/,/^(?:\{include )/,/^(?:\{implode )/,/^(?:\{\/implode\})/,/^(?:\{foreach )/,/^(?:\{foreachelse\})/,/^(?:\{\/foreach\})/,/^(?:\{(?!\s))/,/^(?:\})/,/^(?:\s+)/,/^(?:$)/,/^(?:[^{])/],
-conditions: {"command":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33],"inclusive":true},"INITIAL":{"rules":[0,1,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,31,32,33],"inclusive":true}}
-});
-return lexer;
-})();
-parser.lexer = lexer;
-return parser;
-});
-/**
- * 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
- */
-define('WoltLabSuite/Core/NumberUtil',[], 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;
-});
-
-/**
- * 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     WoltLabSuite/Core/StringUtil
- */
-define('WoltLabSuite/Core/StringUtil',['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;
-               }
-       };
-});
-
-/**
- * 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('WoltLabSuite/Core/Template',['./Template.grammar', './StringUtil', 'Language'], function(parser, StringUtil, Language) {
-       "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", "v", template).bind(undefined, StringUtil, Language);
-               }
-               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;
-});
-
-/**
- * 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     WoltLabSuite/Core/Language
- */
-define('WoltLabSuite/Core/Language',['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;
-});
-
-/**
- * Simple API to store and invoke multiple callbacks per identifier.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/CallbackList
- */
-define('WoltLabSuite/Core/CallbackList',['Dictionary'], function(Dictionary) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function CallbackList() {
-               this._dictionary = new Dictionary();
-       }
-       CallbackList.prototype = {
-               /**
-                * Adds a callback for given identifier.
-                * 
-                * @param       {string}        identifier      arbitrary string to group and identify callbacks
-                * @param       {function}      callback        callback function
-                */
-               add: function(identifier, callback) {
-                       if (typeof callback !== 'function') {
-                               throw new TypeError("Expected a valid callback as second argument for identifier '" + identifier + "'.");
-                       }
-                       
-                       if (!this._dictionary.has(identifier)) {
-                               this._dictionary.set(identifier, []);
-                       }
-                       
-                       this._dictionary.get(identifier).push(callback);
-               },
-               
-               /**
-                * Removes all callbacks registered for given identifier
-                * 
-                * @param       {string}        identifier      arbitrary string to group and identify callbacks
-                */
-               remove: function(identifier) {
-                       this._dictionary['delete'](identifier);
-               },
-               
-               /**
-                * Invokes callback function on each registered callback.
-                * 
-                * @param       {string|null}           identifier      arbitrary string to group and identify callbacks.
-                *                                                      null is a wildcard to match every identifier
-                * @param       {function(function)}    callback        function called with the individual callback as parameter
-                */
-               forEach: function(identifier, callback) {
-                       if (identifier === null) {
-                               this._dictionary.forEach(function(callbacks, identifier) {
-                                       callbacks.forEach(callback);
-                               });
-                       }
-                       else {
-                               var callbacks = this._dictionary.get(identifier);
-                               if (callbacks !== undefined) {
-                                       callbacks.forEach(callback);
-                               }
-                       }
-               }
-       };
-       
-       return CallbackList;
-});
-
-/**
- * Allows to be informed when the DOM may have changed and
- * new elements that are relevant to you may have been added.
- * 
- * @author     Tim Duesterhus
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Dom/Change/Listener
- */
-define('WoltLabSuite/Core/Dom/Change/Listener',['CallbackList'], function(CallbackList) {
-       "use strict";
-       
-       var _callbackList = new CallbackList();
-       var _hot = false;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Dom/Change/Listener
-        */
-       return {
-               /**
-                * @see WoltLabSuite/Core/CallbackList#add
-                */
-               add: _callbackList.add.bind(_callbackList),
-               
-               /**
-                * @see WoltLabSuite/Core/CallbackList#remove
-                */
-               remove: _callbackList.remove.bind(_callbackList),
-               
-               /**
-                * Triggers the execution of all the listeners.
-                * Use this function when you added new elements to the DOM that might
-                * be relevant to others.
-                * While this function is in progress further calls to it will be ignored.
-                */
-               trigger: function() {
-                       if (_hot) return;
-                       
-                       try {
-                               _hot = true;
-                               _callbackList.forEach(null, function(callback) {
-                                       callback();
-                               });
-                       }
-                       finally {
-                               _hot = false;
-                       }
-               }
-       };
-});
-
-/**
- * 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     WoltLabSuite/Core/Environment
- */
-define('WoltLabSuite/Core/Environment',[], 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);
-               },
-               
-               /**
-                * 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;
-               }
-       };
-});
-
-/**
- * Provides helper functions to work with DOM nodes.
- *
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Dom/Util
- */
-define('WoltLabSuite/Core/Dom/Util',['Environment', 'StringUtil'], function(Environment, StringUtil) {
-       "use strict";
-       
-       function _isBoundaryNode(element, ancestor, position) {
-               if (!ancestor.contains(element)) {
-                       throw new Error("Ancestor element does not contain target element.");
-               }
-               
-               var node, whichSibling = position + 'Sibling';
-               while (element !== null && element !== ancestor) {
-                       if (element[position + 'ElementSibling'] !== null) {
-                               return false;
-                       }
-                       else if (element[whichSibling]) {
-                               node = element[whichSibling];
-                               while (node) {
-                                       if (node.textContent.trim() !== '') {
-                                               return false;
-                                       }
-                                       
-                                       node = node[whichSibling];
-                               }
-                       }
-                       
-                       element = element.parentNode;
-               }
-               
-               return true;
-       }
-       
-       var _idCounter = 0;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Dom/Util
-        */
-       var DomUtil = {
-               /**
-                * Returns a DocumentFragment containing the provided HTML string as DOM nodes.
-                * 
-                * @param       {string}        html    HTML string
-                * @return      {DocumentFragment}      fragment containing DOM nodes
-                */
-               createFragmentFromHtml: function(html) {
-                       var tmp = elCreate('div');
-                       this.setInnerHtml(tmp, html);
-                       
-                       var fragment = document.createDocumentFragment();
-                       while (tmp.childNodes.length) {
-                               fragment.appendChild(tmp.childNodes[0]);
-                       }
-                       
-                       return fragment;
-               },
-               
-               /**
-                * Returns a unique element id.
-                * 
-                * @return      {string}        unique id
-                */
-               getUniqueId: function() {
-                       var elementId;
-                       
-                       do {
-                               elementId = 'wcf' + _idCounter++;
-                       }
-                       while (elById(elementId) !== null);
-                       
-                       return elementId;
-               },
-               
-               /**
-                * Returns the element's id. If there is no id set, a unique id will be
-                * created and assigned.
-                * 
-                * @param       {Element}       el      element
-                * @return      {string}        element id
-                */
-               identify: function(el) {
-                       if (!(el instanceof Element)) {
-                               throw new TypeError("Expected a valid DOM element as argument.");
-                       }
-                       
-                       var id = elAttr(el, 'id');
-                       if (!id) {
-                               id = this.getUniqueId();
-                               elAttr(el, 'id', id);
-                       }
-                       
-                       return id;
-               },
-               
-               /**
-                * Returns the outer height of an element including margins.
-                * 
-                * @param       {Element}               el              element
-                * @param       {CSSStyleDeclaration=}  styles          result of window.getComputedStyle()
-                * @return      {int}                   outer height in px
-                */
-               outerHeight: function(el, styles) {
-                       styles = styles || window.getComputedStyle(el);
-                       
-                       var height = el.offsetHeight;
-                       height += ~~styles.marginTop + ~~styles.marginBottom;
-                       
-                       return height;
-               },
-               
-               /**
-                * Returns the outer width of an element including margins.
-                * 
-                * @param       {Element}               el              element
-                * @param       {CSSStyleDeclaration=}  styles          result of window.getComputedStyle()
-                * @return      {int}   outer width in px
-                */
-               outerWidth: function(el, styles) {
-                       styles = styles || window.getComputedStyle(el);
-                       
-                       var width = el.offsetWidth;
-                       width += ~~styles.marginLeft + ~~styles.marginRight;
-                       
-                       return width;
-               },
-               
-               /**
-                * Returns the outer dimensions of an element including margins.
-                * 
-                * @param       {Element}       el              element
-                * @return      {{height: int, width: int}}     dimensions in px
-                */
-               outerDimensions: function(el) {
-                       var styles = window.getComputedStyle(el);
-                       
-                       return {
-                               height: this.outerHeight(el, styles),
-                               width: this.outerWidth(el, styles)
-                       };
-               },
-               
-               /**
-                * Returns the element's offset relative to the document's top left corner.
-                * 
-                * @param       {Element}       el              element
-                * @return      {{left: int, top: int}}         offset relative to top left corner
-                */
-               offset: function(el) {
-                       var rect = el.getBoundingClientRect();
-                       
-                       return {
-                               top: Math.round(rect.top + (window.scrollY || window.pageYOffset)),
-                               left: Math.round(rect.left + (window.scrollX || window.pageXOffset))
-                       };
-               },
-               
-               /**
-                * Prepends an element to a parent element.
-                * 
-                * @param       {Element}       el              element to prepend
-                * @param       {Element}       parentEl        future containing element
-                */
-               prepend: function(el, parentEl) {
-                       if (parentEl.childNodes.length === 0) {
-                               parentEl.appendChild(el);
-                       }
-                       else {
-                               parentEl.insertBefore(el, parentEl.childNodes[0]);
-                       }
-               },
-               
-               /**
-                * Inserts an element after an existing element.
-                * 
-                * @param       {Element}       newEl           element to insert
-                * @param       {Element}       el              reference element
-                */
-               insertAfter: function(newEl, el) {
-                       if (el.nextSibling !== null) {
-                               el.parentNode.insertBefore(newEl, el.nextSibling);
-                       }
-                       else {
-                               el.parentNode.appendChild(newEl);
-                       }
-               },
-               
-               /**
-                * Applies a list of CSS properties to an element.
-                * 
-                * @param       {Element}               el      element
-                * @param       {Object<string, *>}     styles  list of CSS styles
-                */
-               setStyles: function(el, styles) {
-                       var important = false;
-                       for (var property in styles) {
-                               if (styles.hasOwnProperty(property)) {
-                                       if (/ !important$/.test(styles[property])) {
-                                               important = true;
-                                               
-                                               styles[property] = styles[property].replace(/ !important$/, '');
-                                       }
-                                       else {
-                                               important = false;
-                                       }
-                                       
-                                       // for a set style property with priority = important, some browsers are
-                                       // not able to overwrite it with a property != important; removing the
-                                       // property first solves this issue
-                                       if (el.style.getPropertyPriority(property) === 'important' && !important) {
-                                               el.style.removeProperty(property);
-                                       }
-                                       
-                                       el.style.setProperty(property, styles[property], (important ? 'important' : ''));
-                               }
-                       }
-               },
-               
-               /**
-                * Returns a style property value as integer.
-                * 
-                * The behavior of this method is undefined for properties that are not considered
-                * to have a "numeric" value, e.g. "background-image".
-                * 
-                * @param       {CSSStyleDeclaration}   styles          result of window.getComputedStyle()
-                * @param       {string}                propertyName    property name
-                * @return      {int}                   property value as integer
-                */
-               styleAsInt: function(styles, propertyName) {
-                       var value = styles.getPropertyValue(propertyName);
-                       if (value === null) {
-                               return 0;
-                       }
-                       
-                       return parseInt(value);
-               },
-               
-               /**
-                * Sets the inner HTML of given element and reinjects <script> elements to be properly executed.
-                * 
-                * @see         http://www.w3.org/TR/2008/WD-html5-20080610/dom.html#innerhtml0
-                * @param       {Element}       element         target element
-                * @param       {string}        innerHtml       HTML string
-                */
-               setInnerHtml: function(element, innerHtml) {
-                       element.innerHTML = innerHtml;
-                       
-                       var newScript, script, scripts = elBySelAll('script', element);
-                       for (var i = 0, length = scripts.length; i < length; i++) {
-                               script = scripts[i];
-                               newScript = elCreate('script');
-                               if (script.src) {
-                                       newScript.src = script.src;
-                               }
-                               else {
-                                       newScript.textContent = script.textContent;
-                               }
-                               
-                               element.appendChild(newScript);
-                               elRemove(script);
-                       }
-               },
-               
-               /**
-                * 
-                * @param html
-                * @param {Element} referenceElement
-                * @param insertMethod
-                */
-               insertHtml: function(html, referenceElement, insertMethod) {
-                       var element = elCreate('div');
-                       this.setInnerHtml(element, html);
-                       
-                       if (!element.childNodes.length) {
-                               return;
-                       }
-                       
-                       var node = element.childNodes[0];
-                       switch (insertMethod) {
-                               case 'append':
-                                       referenceElement.appendChild(node);
-                                       break;
-                               
-                               case 'after':
-                                       this.insertAfter(node, referenceElement);
-                                       break;
-                               
-                               case 'prepend':
-                                       this.prepend(node, referenceElement);
-                                       break;
-                               
-                               case 'before':
-                                       referenceElement.parentNode.insertBefore(node, referenceElement);
-                                       break;
-                               
-                               default:
-                                       throw new Error("Unknown insert method '" + insertMethod + "'.");
-                                       break;
-                       }
-                       
-                       var tmp;
-                       while (element.childNodes.length) {
-                               tmp = element.childNodes[0];
-                               
-                               this.insertAfter(tmp, node);
-                               node = tmp;
-                       }
-               },
-               
-               /**
-                * Returns true if `element` contains the `child` element.
-                * 
-                * @param       {Element}       element         container element
-                * @param       {Element}       child           child element
-                * @returns     {boolean}       true if `child` is a (in-)direct child of `element`
-                */
-               contains: function(element, child) {
-                       while (child !== null) {
-                               child = child.parentNode;
-                               
-                               if (element === child) {
-                                       return true;
-                               }
-                       }
-                       
-                       return false;
-               },
-               
-               /**
-                * Retrieves all data attributes from target element, optionally allowing for
-                * a custom prefix that serves two purposes: First it will restrict the results
-                * for items starting with it and second it will remove that prefix.
-                * 
-                * @param       {Element}       element         target element
-                * @param       {string=}       prefix          attribute prefix
-                * @param       {boolean=}      camelCaseName  transform attribute names into camel case using dashes as separators
-                * @param       {boolean=}      idToUpperCase   transform '-id' into 'ID'
-                * @returns     {object<string, string>}        list of data attributes
-                */
-               getDataAttributes: function(element, prefix, camelCaseName, idToUpperCase) {
-                       prefix = prefix || '';
-                       if (!/^data-/.test(prefix)) prefix = 'data-' + prefix;
-                       camelCaseName = (camelCaseName === true);
-                       idToUpperCase = (idToUpperCase === true);
-                       
-                       var attribute, attributes = {}, name, tmp;
-                       for (var i = 0, length = element.attributes.length; i < length; i++) {
-                               attribute = element.attributes[i];
-                               
-                               if (attribute.name.indexOf(prefix) === 0) {
-                                       name = attribute.name.replace(new RegExp('^' + prefix), '');
-                                       if (camelCaseName) {
-                                               tmp = name.split('-');
-                                               name = '';
-                                               for (var j = 0, innerLength = tmp.length; j < innerLength; j++) {
-                                                       if (name.length) {
-                                                               if (idToUpperCase && tmp[j] === 'id') {
-                                                                       tmp[j] = 'ID';
-                                                               }
-                                                               else {
-                                                                       tmp[j] = StringUtil.ucfirst(tmp[j]);
-                                                               }
-                                                       }
-                                                       
-                                                       name += tmp[j];
-                                               }
-                                       }
-                                       
-                                       attributes[name] = attribute.value;
-                               }
-                       }
-                       
-                       return attributes;
-               },
-               
-               /**
-                * Unwraps contained nodes by moving them out of `element` while
-                * preserving their previous order. Target element will be removed
-                * at the end of the operation.
-                * 
-                * @param       {Element}       element         target element
-                */
-               unwrapChildNodes: function(element) {
-                       var parent = element.parentNode;
-                       while (element.childNodes.length) {
-                               parent.insertBefore(element.childNodes[0], element);
-                       }
-                       
-                       elRemove(element);
-               },
-               
-               /**
-                * Replaces an element by moving all child nodes into the new element
-                * while preserving their previous order. The old element will be removed
-                * at the end of the operation.
-                * 
-                * @param       {Element}       oldElement      old element
-                * @param       {Element}       newElement      old element
-                */
-               replaceElement: function(oldElement, newElement) {
-                       while (oldElement.childNodes.length) {
-                               newElement.appendChild(oldElement.childNodes[0]);
-                       }
-                       
-                       oldElement.parentNode.insertBefore(newElement, oldElement);
-                       elRemove(oldElement);
-               },
-               
-               /**
-                * Returns true if given element is the most left node of the ancestor, that is
-                * a node without any content nor elements before it or its parent nodes.
-                * 
-                * @param       {Element}       element         target element
-                * @param       {Element}       ancestor        ancestor element, must contain the target element
-                * @returns     {boolean}       true if target element is the most left node
-                */
-               isAtNodeStart: function(element, ancestor) {
-                       return _isBoundaryNode(element, ancestor, 'previous');
-               },
-               
-               /**
-                * Returns true if given element is the most right node of the ancestor, that is
-                * a node without any content nor elements after it or its parent nodes.
-                * 
-                * @param       {Element}       element         target element
-                * @param       {Element}       ancestor        ancestor element, must contain the target element
-                * @returns     {boolean}       true if target element is the most right node
-                */
-               isAtNodeEnd: function(element, ancestor) {
-                       return _isBoundaryNode(element, ancestor, 'next');
-               },
-               
-               /**
-                * Returns the first ancestor element with position fixed or null.
-                * 
-                * @param       {Element}               element         target element
-                * @returns     {(Element|null)}        first ancestor with position fixed or null
-                */
-               getFixedParent: function (element) {
-                       while (element && element !== document.body) {
-                               if (window.getComputedStyle(element).getPropertyValue('position') === 'fixed') {
-                                       return element;
-                               }
-                               
-                               element = element.offsetParent;
-                       }
-                       
-                       return null;
-               }
-       };
-       
-       // expose on window object for backward compatibility
-       window.bc_wcfDomUtil = DomUtil;
-       
-       return DomUtil;
-});
-
-/**
- * Simple `object` to `object` map using a native WeakMap on supported browsers, otherwise a set of two arrays.
- * 
- * 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     WoltLabSuite/Core/ObjectMap
- */
-define('WoltLabSuite/Core/ObjectMap',[], 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;
-});
-
-/**
- * Provides helper functions to traverse the DOM.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Dom/Traverse
- */
-define('WoltLabSuite/Core/Dom/Traverse',[], function() {
-       "use strict";
-       
-       /** @const */ var NONE = 0;
-       /** @const */ var SELECTOR = 1;
-       /** @const */ var CLASS_NAME = 2;
-       /** @const */ var TAG_NAME = 3;
-       
-       var _probe = [
-               function(el, none) { return true; },
-               function(el, selector) { return el.matches(selector); },
-               function(el, className) { return el.classList.contains(className); },
-               function(el, tagName) { return el.nodeName === tagName; }
-       ];
-       
-       var _children = function(el, type, value) {
-               if (!(el instanceof Element)) {
-                       throw new TypeError("Expected a valid element as first argument.");
-               }
-               
-               var children = [];
-               
-               for (var i = 0; i < el.childElementCount; i++) {
-                       if (_probe[type](el.children[i], value)) {
-                               children.push(el.children[i]);
-                       }
-               }
-               
-               return children;
-       };
-       
-       var _parent = function(el, type, value, untilElement) {
-               if (!(el instanceof Element)) {
-                       throw new TypeError("Expected a valid element as first argument.");
-               }
-               
-               el = el.parentNode;
-               
-               while (el instanceof Element) {
-                       if (el === untilElement) {
-                               return null;
-                       }
-                       
-                       if (_probe[type](el, value)) {
-                               return el;
-                       }
-                       
-                       el = el.parentNode;
-               }
-               
-               return null;
-       };
-       
-       var _sibling = function(el, siblingType, type, value) {
-               if (!(el instanceof Element)) {
-                       throw new TypeError("Expected a valid element as first argument.");
-               }
-               
-               if (el instanceof Element) {
-                       if (el[siblingType] !== null && _probe[type](el[siblingType], value)) {
-                               return el[siblingType];
-                       }
-               }
-               
-               return null;
-       };
-       
-       /**
-        * @exports     WoltLabSuite/Core/Dom/Traverse
-        */
-       return {
-               /**
-                * Examines child elements and returns the first child matching the given selector.
-                * 
-                * @param       {Element}               el              element
-                * @param       {string}                selector        CSS selector to match child elements against
-                * @return      {(Element|null)}        null if there is no child node matching the selector
-                */
-               childBySel: function(el, selector) {
-                       return _children(el, SELECTOR, selector)[0] || null;
-               },
-               
-               /**
-                * Examines child elements and returns the first child that has the given CSS class set.
-                * 
-                * @param       {Element}               el              element
-                * @param       {string}                className       CSS class name
-                * @return      {(Element|null)}        null if there is no child node with given CSS class
-                */
-               childByClass: function(el, className) {
-                       return _children(el, CLASS_NAME, className)[0] || null;
-               },
-               
-               /**
-                * Examines child elements and returns the first child which equals the given tag.
-                * 
-                * @param       {Element}               el              element
-                * @param       {string}                tagName         element tag name
-                * @return      {(Element|null)}        null if there is no child node which equals given tag
-                */
-               childByTag: function(el, tagName) {
-                       return _children(el, TAG_NAME, tagName)[0] || null;
-               },
-               
-               /**
-                * Examines child elements and returns all children matching the given selector.
-                * 
-                * @param       {Element}               el              element
-                * @param       {string}                selector        CSS selector to match child elements against
-                * @return      {array<Element>}        list of children matching the selector
-                */
-               childrenBySel: function(el, selector) {
-                       return _children(el, SELECTOR, selector);
-               },
-               
-               /**
-                * Examines child elements and returns all children that have the given CSS class set.
-                * 
-                * @param       {Element}               el              element
-                * @param       {string}                className       CSS class name
-                * @return      {array<Element>}        list of children with the given class
-                */
-               childrenByClass: function(el, className) {
-                       return _children(el, CLASS_NAME, className);
-               },
-               
-               /**
-                * Examines child elements and returns all children which equal the given tag.
-                * 
-                * @param       {Element}               el              element
-                * @param       {string}                tagName         element tag name
-                * @return      {array<Element>}        list of children equaling the tag name
-                */
-               childrenByTag: function(el, tagName) {
-                       return _children(el, TAG_NAME, tagName);
-               },
-               
-               /**
-                * Examines parent nodes and returns the first parent that matches the given selector.
-                * 
-                * @param       {Element}       el              child element
-                * @param       {string}        selector        CSS selector to match parent nodes against
-                * @param       {Element=}      untilElement    stop when reaching this element
-                * @return      {(Element|null)}        null if no parent node matched the selector
-                */
-               parentBySel: function(el, selector, untilElement) {
-                       return _parent(el, SELECTOR, selector, untilElement);
-               },
-               
-               /**
-                * Examines parent nodes and returns the first parent that has the given CSS class set.
-                * 
-                * @param       {Element}       el              child element
-                * @param       {string}        className       CSS class name
-                * @param       {Element=}      untilElement    stop when reaching this element
-                * @return      {(Element|null)}        null if there is no parent node with given class
-                */
-               parentByClass: function(el, className, untilElement) {
-                       return _parent(el, CLASS_NAME, className, untilElement);
-               },
-               
-               /**
-                * Examines parent nodes and returns the first parent which equals the given tag.
-                * 
-                * @param       {Element}       el              child element
-                * @param       {string}        tagName         element tag name
-                * @param       {Element=}      untilElement    stop when reaching this element
-                * @return      {(Element|null)}        null if there is no parent node of given tag type
-                */
-               parentByTag: function(el, tagName, untilElement) {
-                       return _parent(el, TAG_NAME, tagName, untilElement);
-               },
-               
-               /**
-                * Returns the next element sibling.
-                * 
-                * @param       {Element}       el              element
-                * @return      {(Element|null)}        null if there is no next sibling element
-                */
-               next: function(el) {
-                       return _sibling(el, 'nextElementSibling', NONE, null);
-               },
-               
-               /**
-                * Returns the next element sibling that matches the given selector.
-                * 
-                * @param       {Element}       el              element
-                * @param       {string}        selector        CSS selector to match parent nodes against
-                * @return      {(Element|null)}        null if there is no next sibling element or it does not match the selector
-                */
-               nextBySel: function(el, selector) {
-                       return _sibling(el, 'nextElementSibling', SELECTOR, selector);
-               },
-               
-               /**
-                * Returns the next element sibling with given CSS class.
-                * 
-                * @param       {Element}       el              element
-                * @param       {string}        className       CSS class name
-                * @return      {(Element|null)}        null if there is no next sibling element or it does not have the class set
-                */
-               nextByClass: function(el, className) {
-                       return _sibling(el, 'nextElementSibling', CLASS_NAME, className);
-               },
-               
-               /**
-                * Returns the next element sibling with given CSS class.
-                * 
-                * @param       {Element}       el              element
-                * @param       {string}        tagName         element tag name
-                * @return      {(Element|null)}        null if there is no next sibling element or it does not have the class set
-                */
-               nextByTag: function(el, tagName) {
-                       return _sibling(el, 'nextElementSibling', TAG_NAME, tagName);
-               },
-               
-               /**
-                * Returns the previous element sibling.
-                * 
-                * @param       {Element}       el              element
-                * @return      {(Element|null)}        null if there is no previous sibling element
-                */
-               prev: function(el) {
-                       return _sibling(el, 'previousElementSibling', NONE, null);
-               },
-               
-               /**
-                * Returns the previous element sibling that matches the given selector.
-                * 
-                * @param       {Element}       el              element
-                * @param       {string}        selector        CSS selector to match parent nodes against
-                * @return      {(Element|null)}        null if there is no previous sibling element or it does not match the selector
-                */
-               prevBySel: function(el, selector) {
-                       return _sibling(el, 'previousElementSibling', SELECTOR, selector);
-               },
-               
-               /**
-                * Returns the previous element sibling with given CSS class.
-                * 
-                * @param       {Element}       el              element
-                * @param       {string}        className       CSS class name
-                * @return      {(Element|null)}        null if there is no previous sibling element or it does not have the class set
-                */
-               prevByClass: function(el, className) {
-                       return _sibling(el, 'previousElementSibling', CLASS_NAME, className);
-               },
-               
-               /**
-                * Returns the previous element sibling with given CSS class.
-                * 
-                * @param       {Element}       el              element
-                * @param       {string}        tagName         element tag name
-                * @return      {(Element|null)}        null if there is no previous sibling element or it does not have the class set
-                */
-               prevByTag: function(el, tagName) {
-                       return _sibling(el, 'previousElementSibling', TAG_NAME, tagName);
-               }
-       };
-});
-
-/**
- * Provides the confirmation dialog overlay.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Confirmation
- */
-define('WoltLabSuite/Core/Ui/Confirmation',['Core', 'Language', 'Ui/Dialog'], function(Core, Language, UiDialog) {
-       "use strict";
-       
-       var _active = false;
-       var _confirmButton = null;
-       var _content = null;
-       var _options = {};
-       var _text = null;
-       
-       /**
-        * Confirmation dialog overlay.
-        * 
-        * @exports     WoltLabSuite/Core/Ui/Confirmation
-        */
-       return {
-               /**
-                * Shows the confirmation dialog.
-                * 
-                * Possible options:
-                *  - cancel: callback if user cancels the dialog
-                *  - confirm: callback if user confirm the dialog
-                *  - legacyCallback: WCF 2.0/2.1 compatible callback with string parameter
-                *  - message: displayed confirmation message
-                *  - parameters: list of parameters passed to the callback on confirm
-                *  - template: optional HTML string to be inserted below the `message`
-                * 
-                * @param       {object<string, *>}     options         confirmation options
-                */
-               show: function(options) {
-                       if (UiDialog === undefined) UiDialog = require('Ui/Dialog');
-                       
-                       if (_active) {
-                               return;
-                       }
-                       
-                       _options = Core.extend({
-                               cancel: null,
-                               confirm: null,
-                               legacyCallback: null,
-                               message: '',
-                               messageIsHtml: false,
-                               parameters: {},
-                               template: ''
-                       }, options);
-                       
-                       _options.message = (typeof _options.message === 'string') ? _options.message.trim() : '';
-                       if (!_options.message.length) {
-                               throw new Error("Expected a non-empty string for option 'message'.");
-                       }
-                       
-                       if (typeof _options.confirm !== 'function' && typeof _options.legacyCallback !== 'function') {
-                               throw new TypeError("Expected a valid callback for option 'confirm'.");
-                       }
-                       
-                       if (_content === null) {
-                               this._createDialog();
-                       }
-                       
-                       _content.innerHTML = (typeof _options.template === 'string') ? _options.template.trim() : '';
-                       if (_options.messageIsHtml) _text.innerHTML = _options.message;
-                       else _text.textContent = _options.message;
-                       
-                       _active = true;
-                       
-                       UiDialog.open(this);
-               },
-               
-               _dialogSetup: function() {
-                       return {
-                               id: 'wcfSystemConfirmation',
-                               options: {
-                                       onClose: this._onClose.bind(this),
-                                       onShow: this._onShow.bind(this),
-                                       title: Language.get('wcf.global.confirmation.title')
-                               }
-                       };
-               },
-               
-               /**
-                * Returns content container element.
-                * 
-                * @return      {Element}       content container element
-                */
-               getContentElement: function() {
-                       return _content;
-               },
-               
-               /**
-                * Creates the dialog DOM elements.
-                */
-               _createDialog: function() {
-                       var dialog = elCreate('div');
-                       elAttr(dialog, 'id', 'wcfSystemConfirmation');
-                       dialog.classList.add('systemConfirmation');
-                       
-                       _text = elCreate('p');
-                       dialog.appendChild(_text);
-                       
-                       _content = elCreate('div');
-                       elAttr(_content, 'id', 'wcfSystemConfirmationContent');
-                       dialog.appendChild(_content);
-                       
-                       var formSubmit = elCreate('div');
-                       formSubmit.classList.add('formSubmit');
-                       dialog.appendChild(formSubmit);
-                       
-                       _confirmButton = elCreate('button');
-                       _confirmButton.classList.add('buttonPrimary');
-                       _confirmButton.textContent = Language.get('wcf.global.confirmation.confirm');
-                       _confirmButton.addEventListener(WCF_CLICK_EVENT, this._confirm.bind(this));
-                       formSubmit.appendChild(_confirmButton);
-                       
-                       var cancelButton = elCreate('button');
-                       cancelButton.textContent = Language.get('wcf.global.confirmation.cancel');
-                       cancelButton.addEventListener(WCF_CLICK_EVENT, function() { UiDialog.close('wcfSystemConfirmation'); });
-                       formSubmit.appendChild(cancelButton);
-                       
-                       document.body.appendChild(dialog);
-               },
-               
-               /**
-                * Invoked if the user confirms the dialog.
-                */
-               _confirm: function() {
-                       if (typeof _options.legacyCallback === 'function') {
-                               _options.legacyCallback('confirm', _options.parameters, _content);
-                       }
-                       else {
-                               _options.confirm(_options.parameters, _content);
-                       }
-                       
-                       _active = false;
-                       UiDialog.close('wcfSystemConfirmation');
-               },
-               
-               /**
-                * Invoked on dialog close or if user cancels the dialog.
-                */
-               _onClose: function() {
-                       if (_active) {
-                               _confirmButton.blur();
-                               _active = false;
-                               
-                               if (typeof _options.legacyCallback === 'function') {
-                                       _options.legacyCallback('cancel', _options.parameters, _content);
-                               }
-                               else if (typeof _options.cancel === 'function') {
-                                       _options.cancel(_options.parameters);
-                               }
-                       }
-               },
-               
-               /**
-                * Sets the focus on the confirm button on dialog open for proper keyboard support.
-                */
-               _onShow: function() {
-                       _confirmButton.blur();
-                       _confirmButton.focus();
-               }
-       };
-});
-
-/**
- * Provides consistent support for media queries and body scrolling.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Screen
- */
-define('WoltLabSuite/Core/Ui/Screen',['Core', 'Dictionary', 'Environment'], function(Core, Dictionary, Environment) {
-       "use strict";
-       
-       var _dialogContainer = null;
-       var _mql = new Dictionary();
-       var _scrollDisableCounter = 0;
-       var _scrollOffsetFrom = null;
-       var _scrollTop = 0;
-       var _pageOverlayCounter = 0;
-       
-       var _mqMap = Dictionary.fromObject({
-               'screen-xs': '(max-width: 544px)',                              /* smartphone */
-               'screen-sm': '(min-width: 545px) and (max-width: 768px)',       /* tablet (portrait) */
-               'screen-sm-down': '(max-width: 768px)',                         /* smartphone + tablet (portrait) */
-               'screen-sm-up': '(min-width: 545px)',                           /* tablet (portrait) + tablet (landscape) + desktop */
-               'screen-sm-md': '(min-width: 545px) and (max-width: 1024px)',   /* tablet (portrait) + tablet (landscape) */
-               'screen-md': '(min-width: 769px) and (max-width: 1024px)',      /* tablet (landscape) */
-               'screen-md-down': '(max-width: 1024px)',                        /* smartphone + tablet (portrait) + tablet (landscape) */
-               'screen-md-up': '(min-width: 769px)',                           /* tablet (landscape) + desktop */
-               'screen-lg': '(min-width: 1025px)'                              /* desktop */
-       });
-       
-       // Microsoft Edge rewrites the media queries to whatever it
-       // pleases, causing the input and output query to mismatch
-       var _mqMapEdge = new Dictionary();
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Screen
-        */
-       return {
-               /**
-                * Registers event listeners for media query match/unmatch.
-                * 
-                * The `callbacks` object may contain the following keys:
-                *  - `match`, triggered when media query matches
-                *  - `unmatch`, triggered when media query no longer matches
-                *  - `setup`, invoked when media query first matches
-                * 
-                * Returns a UUID that is used to internal identify the callbacks, can be used
-                * to remove binding by calling the `remove` method.
-                * 
-                * @param       {string}        query           media query
-                * @param       {object}        callbacks       callback functions
-                * @return      {string}        UUID for listener removal
-                */
-               on: function(query, callbacks) {
-                       var uuid = Core.getUuid(), queryObject = this._getQueryObject(query);
-                       
-                       if (typeof callbacks.match === 'function') {
-                               queryObject.callbacksMatch.set(uuid, callbacks.match);
-                       }
-                       
-                       if (typeof callbacks.unmatch === 'function') {
-                               queryObject.callbacksUnmatch.set(uuid, callbacks.unmatch);
-                       }
-                       
-                       if (typeof callbacks.setup === 'function') {
-                               if (queryObject.mql.matches) {
-                                       callbacks.setup();
-                               }
-                               else {
-                                       queryObject.callbacksSetup.set(uuid, callbacks.setup);
-                               }
-                       }
-                       
-                       return uuid;
-               },
-               
-               /**
-                * Removes all listeners identified by their common UUID.
-                *
-                * @param       {string}        query   must match the `query` argument used when calling `on()`
-                * @param       {string}        uuid    UUID received when calling `on()`
-                */
-               remove: function(query, uuid) {
-                       var queryObject = this._getQueryObject(query);
-                       
-                       queryObject.callbacksMatch.delete(uuid);
-                       queryObject.callbacksUnmatch.delete(uuid);
-                       queryObject.callbacksSetup.delete(uuid);
-               },
-               
-               /**
-                * Returns a boolean value if a media query expression currently matches.
-                * 
-                * @param       {string}        query   CSS media query
-                * @returns     {boolean}       true if query matches
-                */
-               is: function(query) {
-                       return this._getQueryObject(query).mql.matches;
-               },
-               
-               /**
-                * Disables scrolling of body element.
-                */
-               scrollDisable: function() {
-                       if (_scrollDisableCounter === 0) {
-                               _scrollTop = document.body.scrollTop;
-                               _scrollOffsetFrom = 'body';
-                               if (!_scrollTop) {
-                                       _scrollTop = document.documentElement.scrollTop;
-                                       _scrollOffsetFrom = 'documentElement';
-                               }
-                               
-                               var pageContainer = elById('pageContainer');
-                               
-                               // setting translateY causes Mobile Safari to snap
-                               if (Environment.platform() === 'ios') {
-                                       pageContainer.style.setProperty('position', 'relative', '');
-                                       pageContainer.style.setProperty('top', '-' + _scrollTop + 'px', '');
-                               }
-                               else {
-                                       pageContainer.style.setProperty('margin-top', '-' + _scrollTop + 'px', '');
-                               }
-                               
-                               document.documentElement.classList.add('disableScrolling');
-                       }
-                       
-                       _scrollDisableCounter++;
-               },
-               
-               /**
-                * Re-enables scrolling of body element.
-                */
-               scrollEnable: function() {
-                       if (_scrollDisableCounter) {
-                               _scrollDisableCounter--;
-                               
-                               if (_scrollDisableCounter === 0) {
-                                       document.documentElement.classList.remove('disableScrolling');
-                                       
-                                       var pageContainer = elById('pageContainer');
-                                       if (Environment.platform() === 'ios') {
-                                               pageContainer.style.removeProperty('position');
-                                               pageContainer.style.removeProperty('top');
-                                       }
-                                       else {
-                                               pageContainer.style.removeProperty('margin-top');
-                                       }
-                                       
-                                       if (_scrollTop) {
-                                               document[_scrollOffsetFrom].scrollTop = ~~_scrollTop;
-                                       }
-                               }
-                       }
-               },
-               
-               /**
-                * Indicates that at least one page overlay is currently open.
-                */
-               pageOverlayOpen: function() {
-                       if (_pageOverlayCounter === 0) {
-                               document.documentElement.classList.add('pageOverlayActive');
-                       }
-                       
-                       _pageOverlayCounter++;
-               },
-               
-               /**
-                * Marks one page overlay as closed.
-                */
-               pageOverlayClose: function() {
-                       if (_pageOverlayCounter) {
-                               _pageOverlayCounter--;
-                               
-                               if (_pageOverlayCounter === 0) {
-                                       document.documentElement.classList.remove('pageOverlayActive');
-                               }
-                       }
-               },
-               
-               /**
-                * Returns true if at least one page overlay is currently open.
-                * 
-                * @returns {boolean}
-                */
-               pageOverlayIsActive: function() {
-                       return _pageOverlayCounter > 0;
-               },
-               
-               /**
-                * Sets the dialog container element. This method is used to
-                * circumvent a possible circular dependency, due to `Ui/Dialog`
-                * requiring the `Ui/Screen` module itself.
-                * 
-                * @param       {Element}       container       dialog container element
-                */
-               setDialogContainer: function (container) {
-                       _dialogContainer = container;
-               },
-               
-               /**
-                * 
-                * @param       {string}        query   CSS media query
-                * @return      {Object}        object containing callbacks and MediaQueryList
-                * @protected
-                */
-               _getQueryObject: function(query) {
-                       if (typeof query !== 'string' || query.trim() === '') {
-                               throw new TypeError("Expected a non-empty string for parameter 'query'.");
-                       }
-                       
-                       // Microsoft Edge rewrites the media queries to whatever it
-                       // pleases, causing the input and output query to mismatch
-                       if (_mqMapEdge.has(query)) query = _mqMapEdge.get(query);
-                       
-                       if (_mqMap.has(query)) query = _mqMap.get(query);
-                       
-                       var queryObject = _mql.get(query);
-                       if (!queryObject) {
-                               queryObject = {
-                                       callbacksMatch: new Dictionary(),
-                                       callbacksUnmatch: new Dictionary(),
-                                       callbacksSetup: new Dictionary(),
-                                       mql: window.matchMedia(query)
-                               };
-                               queryObject.mql.addListener(this._mqlChange.bind(this));
-                               
-                               _mql.set(query, queryObject);
-                               
-                               if (query !== queryObject.mql.media) {
-                                       _mqMapEdge.set(queryObject.mql.media, query);
-                               }
-                       }
-                       
-                       return queryObject;
-               },
-               
-               /**
-                * Triggered whenever a registered media query now matches or no longer matches.
-                * 
-                * @param       {Event} event   event object
-                * @protected
-                */
-               _mqlChange: function(event) {
-                       var queryObject = this._getQueryObject(event.media);
-                       if (event.matches) {
-                               if (queryObject.callbacksSetup.size) {
-                                       queryObject.callbacksSetup.forEach(function(callback) {
-                                               callback();
-                                       });
-                                       
-                                       // discard all setup callbacks after execution
-                                       queryObject.callbacksSetup = new Dictionary();
-                               }
-                               else {
-                                       queryObject.callbacksMatch.forEach(function (callback) {
-                                               callback();
-                                       });
-                               }
-                       }
-                       else {
-                               queryObject.callbacksUnmatch.forEach(function(callback) {
-                                       callback();
-                               });
-                       }
-               }
-       };
-});
-
-/**
- * Provides reliable checks for common key presses, uses `Event.key` on supported browsers
- * or the deprecated `Event.which`.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Event/Key
- */
-define('WoltLabSuite/Core/Event/Key',[], function() {
-       "use strict";
-       
-       function _isKey(event, key, which) {
-               if (!(event instanceof Event)) {
-                       throw new TypeError("Expected a valid event when testing for key '" + key + "'.");
-               }
-               
-               return event.key === key || event.which === which;
-       }
-       
-       /**
-        * @exports     WoltLabSuite/Core/Event/Key
-        */
-       return {
-               /**
-                * Returns true if the pressed key equals 'ArrowDown'.
-                * 
-                * @param       {Event}         event           event object
-                * @return      {boolean}
-                */
-               ArrowDown: function(event) {
-                       return _isKey(event, 'ArrowDown', 40);
-               },
-               
-               /**
-                * Returns true if the pressed key equals 'ArrowLeft'.
-                * 
-                * @param       {Event}         event           event object
-                * @return      {boolean}
-                */
-               ArrowLeft: function(event) {
-                       return _isKey(event, 'ArrowLeft', 37);
-               },
-               
-               /**
-                * Returns true if the pressed key equals 'ArrowRight'.
-                * 
-                * @param       {Event}         event           event object
-                * @return      {boolean}
-                */
-               ArrowRight: function(event) {
-                       return _isKey(event, 'ArrowRight', 39);
-               },
-               
-               /**
-                * Returns true if the pressed key equals 'ArrowUp'.
-                * 
-                * @param       {Event}         event           event object
-                * @return      {boolean}
-                */
-               ArrowUp: function(event) {
-                       return _isKey(event, 'ArrowUp', 38);
-               },
-               
-               /**
-                * Returns true if the pressed key equals 'Comma'.
-                * 
-                * @param       {Event}         event           event object
-                * @return      {boolean}
-                */
-               Comma: function(event) {
-                       return _isKey(event, ',', 44);
-               },
-               
-               /**
-                * Returns true if the pressed key equals 'End'.
-                *
-                * @param       {Event}         event           event object
-                * @return      {boolean}
-                */
-               End: function(event) {
-                       return _isKey(event, 'End', 35);
-               },
-               
-               /**
-                * Returns true if the pressed key equals 'Enter'.
-                * 
-                * @param       {Event}         event           event object
-                * @return      {boolean}
-                */
-               Enter: function(event) {
-                       return _isKey(event, 'Enter', 13);
-               },
-               
-               /**
-                * Returns true if the pressed key equals 'Escape'.
-                * 
-                * @param       {Event}         event           event object
-                * @return      {boolean}
-                */
-               Escape: function(event) {
-                       return _isKey(event, 'Escape', 27);
-               },
-               
-               /**
-                * Returns true if the pressed key equals 'Home'.
-                *
-                * @param       {Event}         event           event object
-                * @return      {boolean}
-                */
-               Home: function(event) {
-                       return _isKey(event, 'Home', 36);
-               },
-               
-               /**
-                * Returns true if the pressed key equals 'Space'.
-                *
-                * @param       {Event}         event           event object
-                * @return      {boolean}
-                */
-               Space: function(event) {
-                       return _isKey(event, 'Space', 32);
-               },
-               
-               /**
-                * Returns true if the pressed key equals 'Tab'.
-                * 
-                * @param       {Event}         event           event object
-                * @return      {boolean}
-                */
-               Tab: function(event) {
-                       return _isKey(event, 'Tab', 9);
-               }
-       };
-});
-
-/**
- * Utility class to align elements relatively to another.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Alignment
- */
-define('WoltLabSuite/Core/Ui/Alignment',['Core', 'Language', 'Dom/Traverse', 'Dom/Util'], function(Core, Language, DomTraverse, DomUtil) {
-       "use strict";
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Alignment
-        */
-       return {
-               /**
-                * Sets the alignment for target element relatively to the reference element.
-                * 
-                * @param       {Element}               el              target element
-                * @param       {Element}               ref             reference element
-                * @param       {Object<string, *>}     options         list of options to alter the behavior
-                */
-               set: function(el, ref, options) {
-                       options = Core.extend({
-                               // offset to reference element
-                               verticalOffset: 0,
-                               
-                               // align the pointer element, expects .elementPointer as a direct child of given element
-                               pointer: false,
-                               
-                               // offset from/left side, ignored for center alignment
-                               pointerOffset: 4,
-                               
-                               // use static pointer positions, expects two items: class to move it to the bottom and the second to move it to the right
-                               pointerClassNames: [],
-                               
-                               // alternate element used to calculate dimensions
-                               refDimensionsElement: null,
-                               
-                               // preferred alignment, possible values: left/right/center and top/bottom
-                               horizontal: 'left',
-                               vertical: 'bottom',
-                               
-                               // allow flipping over axis, possible values: both, horizontal, vertical and none
-                               allowFlip: 'both'
-                       }, options);
-                       
-                       if (!Array.isArray(options.pointerClassNames) || options.pointerClassNames.length !== (options.pointer ? 1 : 2)) options.pointerClassNames = [];
-                       if (['left', 'right', 'center'].indexOf(options.horizontal) === -1) options.horizontal = 'left';
-                       if (options.vertical !== 'bottom') options.vertical = 'top';
-                       if (['both', 'horizontal', 'vertical', 'none'].indexOf(options.allowFlip) === -1) options.allowFlip = 'both';
-                       
-                       // place element in the upper left corner to prevent calculation issues due to possible scrollbars
-                       DomUtil.setStyles(el, {
-                               bottom: 'auto !important',
-                               left: '0 !important',
-                               right: 'auto !important',
-                               top: '0 !important',
-                               visibility: 'hidden !important'
-                       });
-                       
-                       var elDimensions = DomUtil.outerDimensions(el);
-                       var refDimensions = DomUtil.outerDimensions((options.refDimensionsElement instanceof Element ? options.refDimensionsElement : ref));
-                       var refOffsets = DomUtil.offset(ref);
-                       var windowHeight = window.innerHeight;
-                       var windowWidth = document.body.clientWidth;
-                       
-                       var horizontal = { result: null };
-                       var alignCenter = false;
-                       if (options.horizontal === 'center') {
-                               alignCenter = true;
-                               horizontal = this._tryAlignmentHorizontal(options.horizontal, elDimensions, refDimensions, refOffsets, windowWidth);
-                               
-                               if (!horizontal.result) {
-                                       if (options.allowFlip === 'both' || options.allowFlip === 'horizontal') {
-                                               options.horizontal = 'left';
-                                       }
-                                       else {
-                                               horizontal.result = true;
-                                       }
-                               }
-                       }
-                       
-                       // in rtl languages we simply swap the value for 'horizontal'
-                       if (Language.get('wcf.global.pageDirection') === 'rtl') {
-                               options.horizontal = (options.horizontal === 'left') ? 'right' : 'left';
-                       }
-                       
-                       if (!horizontal.result) {
-                               var horizontalCenter = horizontal;
-                               horizontal = this._tryAlignmentHorizontal(options.horizontal, elDimensions, refDimensions, refOffsets, windowWidth);
-                               if (!horizontal.result && (options.allowFlip === 'both' || options.allowFlip === 'horizontal')) {
-                                       var horizontalFlipped = this._tryAlignmentHorizontal((options.horizontal === 'left' ? 'right' : 'left'), elDimensions, refDimensions, refOffsets, windowWidth);
-                                       // only use these results if it fits into the boundaries, otherwise both directions exceed and we honor the demanded direction
-                                       if (horizontalFlipped.result) {
-                                               horizontal = horizontalFlipped;
-                                       }
-                                       else if (alignCenter) {
-                                               horizontal = horizontalCenter;
-                                       }
-                               }
-                       }
-                       
-                       var left = horizontal.left;
-                       var right = horizontal.right;
-                       
-                       var vertical = this._tryAlignmentVertical(options.vertical, elDimensions, refDimensions, refOffsets, windowHeight, options.verticalOffset);
-                       if (!vertical.result && (options.allowFlip === 'both' || options.allowFlip === 'vertical')) {
-                               var verticalFlipped = this._tryAlignmentVertical((options.vertical === 'top' ? 'bottom' : 'top'), elDimensions, refDimensions, refOffsets, windowHeight, options.verticalOffset);
-                               // only use these results if it fits into the boundaries, otherwise both directions exceed and we honor the demanded direction
-                               if (verticalFlipped.result) {
-                                       vertical = verticalFlipped;
-                               }
-                       }
-                       
-                       var bottom = vertical.bottom;
-                       var top = vertical.top;
-                       
-                       // set pointer position
-                       if (options.pointer) {
-                               var pointer = DomTraverse.childrenByClass(el, 'elementPointer');
-                               pointer = pointer[0] || null;
-                               if (pointer === null) {
-                                       throw new Error("Expected the .elementPointer element to be a direct children.");
-                               }
-                               
-                               if (horizontal.align === 'center') {
-                                       pointer.classList.add('center');
-                                       
-                                       pointer.classList.remove('left');
-                                       pointer.classList.remove('right');
-                               }
-                               else {
-                                       pointer.classList.add(horizontal.align);
-                                       
-                                       pointer.classList.remove('center');
-                                       pointer.classList.remove(horizontal.align === 'left' ? 'right' : 'left');
-                               }
-                               
-                               if (vertical.align === 'top') {
-                                       pointer.classList.add('flipVertical');
-                               }
-                               else {
-                                       pointer.classList.remove('flipVertical');
-                               }
-                       }
-                       else if (options.pointerClassNames.length === 2) {
-                               var pointerBottom = 0;
-                               var pointerRight = 1;
-                               
-                               el.classList[(top === 'auto' ? 'add' : 'remove')](options.pointerClassNames[pointerBottom]);
-                               el.classList[(left === 'auto' ? 'add' : 'remove')](options.pointerClassNames[pointerRight]);
-                       }
-                       
-                       if (bottom !== 'auto') bottom = Math.round(bottom) + 'px';
-                       if (left !== 'auto') left = Math.ceil(left) + 'px';
-                       if (right !== 'auto') right = Math.floor(right) + 'px';
-                       if (top !== 'auto') top = Math.round(top) + 'px';
-                       
-                       DomUtil.setStyles(el, {
-                               bottom: bottom,
-                               left: left,
-                               right: right,
-                               top: top
-                       });
-                       
-                       elShow(el);
-                       el.style.removeProperty('visibility');
-               },
-               
-               /**
-                * Calculates left/right position and verifies if the element would be still within the page's boundaries.
-                * 
-                * @param       {string}                align           align to this side of the reference element
-                * @param       {Object<string, int>}   elDimensions    element dimensions
-                * @param       {Object<string, int>}   refDimensions   reference element dimensions
-                * @param       {Object<string, int>}   refOffsets      position of reference element relative to the document
-                * @param       {int}                   windowWidth     window width
-                * @returns     {Object<string, *>}     calculation results
-                */
-               _tryAlignmentHorizontal: function(align, elDimensions, refDimensions, refOffsets, windowWidth) {
-                       var left = 'auto';
-                       var right = 'auto';
-                       var result = true;
-                       
-                       if (align === 'left') {
-                               left = refOffsets.left;
-                               if (left + elDimensions.width > windowWidth) {
-                                       result = false;
-                               }
-                       }
-                       else if (align === 'right') {
-                               if (refOffsets.left + refDimensions.width < elDimensions.width) {
-                                       result = false;
-                               }
-                               else {
-                                       right = windowWidth - (refOffsets.left + refDimensions.width);
-                                       if (right < 0) {
-                                               result = false;
-                                       }
-                               }
-                       }
-                       else {
-                               left = refOffsets.left + (refDimensions.width / 2) - (elDimensions.width / 2);
-                               left = ~~left;
-                               
-                               if (left < 0 || left + elDimensions.width > windowWidth) {
-                                       result = false;
-                               }
-                       }
-                       
-                       return {
-                               align: align,
-                               left: left,
-                               right: right,
-                               result: result
-                       };
-               },
-               
-               /**
-                * Calculates top/bottom position and verifies if the element would be still within the page's boundaries.
-                * 
-                * @param       {string}                align           align to this side of the reference element
-                * @param       {Object<string, int>}   elDimensions    element dimensions
-                * @param       {Object<string, int>}   refDimensions   reference element dimensions
-                * @param       {Object<string, int>}   refOffsets      position of reference element relative to the document
-                * @param       {int}                   windowHeight    window height
-                * @param       {int}                   verticalOffset  desired gap between element and reference element
-                * @returns     {object<string, *>}     calculation results
-                */
-               _tryAlignmentVertical: function(align, elDimensions, refDimensions, refOffsets, windowHeight, verticalOffset) {
-                       var bottom = 'auto';
-                       var top = 'auto';
-                       var result = true;
-                       
-                       if (align === 'top') {
-                               var bodyHeight = document.body.clientHeight;
-                               bottom = (bodyHeight - refOffsets.top) + verticalOffset;
-                               if (bodyHeight - (bottom + elDimensions.height) < (window.scrollY || window.pageYOffset)) {
-                                       result = false;
-                               }
-                       }
-                       else {
-                               top = refOffsets.top + refDimensions.height + verticalOffset;
-                               if (top + elDimensions.height - (window.scrollY || window.pageYOffset) > windowHeight) {
-                                       result = false;
-                               }
-                       }
-                       
-                       return {
-                               align: align,
-                               bottom: bottom,
-                               top: top,
-                               result: result
-                       };
-               }
-       };
-});
-
-/**
- * Allows to be informed when a click event bubbled up to the document's body.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/CloseOverlay
- */
-define('WoltLabSuite/Core/Ui/CloseOverlay',['CallbackList'], function(CallbackList) {
-       "use strict";
-       
-       var _callbackList = new CallbackList();
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/CloseOverlay
-        */
-       var UiCloseOverlay = {
-               /**
-                * Sets up global event listener for bubbled clicks events.
-                */
-               setup: function() {
-                       document.body.addEventListener(WCF_CLICK_EVENT, this.execute.bind(this));
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/CallbackList#add
-                */
-               add: _callbackList.add.bind(_callbackList),
-               
-               /**
-                * @see WoltLabSuite/Core/CallbackList#remove
-                */
-               remove: _callbackList.remove.bind(_callbackList),
-               
-               /**
-                * Invokes all registered callbacks.
-                */
-               execute: function() {
-                       _callbackList.forEach(null, function(callback) {
-                               callback();
-                       });
-               }
-       };
-       
-       UiCloseOverlay.setup();
-       
-       return UiCloseOverlay;
-});
-
-/**
- * Simple dropdown implementation.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Dropdown/Simple
- */
-define(
-       'WoltLabSuite/Core/Ui/Dropdown/Simple',[       'CallbackList', 'Core', 'Dictionary', 'EventKey', 'Ui/Alignment', 'Dom/ChangeListener', 'Dom/Traverse', 'Dom/Util', 'Ui/CloseOverlay'],
-       function(CallbackList,   Core,   Dictionary,   EventKey,   UiAlignment,    DomChangeListener,    DomTraverse,    DomUtil,    UiCloseOverlay)
-{
-       "use strict";
-       
-       var _availableDropdowns = null;
-       var _callbacks = new CallbackList();
-       var _didInit = false;
-       var _dropdowns = new Dictionary();
-       var _menus = new Dictionary();
-       var _menuContainer = null;
-       var _callbackDropdownMenuKeyDown =  null;
-       var _activeTargetId = '';
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Dropdown/Simple
-        */
-       return {
-               /**
-                * Performs initial setup such as setting up dropdowns and binding listeners.
-                */
-               setup: function() {
-                       if (_didInit) return;
-                       _didInit = true;
-                       
-                       _menuContainer = elCreate('div');
-                       _menuContainer.className = 'dropdownMenuContainer';
-                       document.body.appendChild(_menuContainer);
-                       
-                       _availableDropdowns = elByClass('dropdownToggle');
-                       
-                       this.initAll();
-                       
-                       UiCloseOverlay.add('WoltLabSuite/Core/Ui/Dropdown/Simple', this.closeAll.bind(this));
-                       DomChangeListener.add('WoltLabSuite/Core/Ui/Dropdown/Simple', this.initAll.bind(this));
-                       
-                       document.addEventListener('scroll', this._onScroll.bind(this));
-                       
-                       // expose on window object for backward compatibility
-                       window.bc_wcfSimpleDropdown = this;
-                       
-                       _callbackDropdownMenuKeyDown = this._dropdownMenuKeyDown.bind(this);
-               },
-               
-               /**
-                * Loops through all possible dropdowns and registers new ones.
-                */
-               initAll: function() {
-                       for (var i = 0, length = _availableDropdowns.length; i < length; i++) {
-                               this.init(_availableDropdowns[i], false);
-                       }
-               },
-               
-               /**
-                * Initializes a dropdown.
-                * 
-                * @param       {Element}       button
-                * @param       {boolean|Event} isLazyInitialization
-                */
-               init: function(button, isLazyInitialization) {
-                       this.setup();
-                       
-                       elAttr(button, 'role', 'button');
-                       elAttr(button, 'tabindex', '0');
-                       elAttr(button, 'aria-haspopup', true);
-                       elAttr(button, 'aria-expanded', false);
-                       
-                       if (button.classList.contains('jsDropdownEnabled') || elData(button, 'target')) {
-                               return false;
-                       }
-                       
-                       var dropdown = DomTraverse.parentByClass(button, 'dropdown');
-                       if (dropdown === null) {
-                               throw new Error("Invalid dropdown passed, button '" + DomUtil.identify(button) + "' does not have a parent with .dropdown.");
-                       }
-                       
-                       var menu = DomTraverse.nextByClass(button, 'dropdownMenu');
-                       if (menu === null) {
-                               throw new Error("Invalid dropdown passed, button '" + DomUtil.identify(button) + "' does not have a menu as next sibling.");
-                       }
-                       
-                       // move menu into global container
-                       _menuContainer.appendChild(menu);
-                       
-                       var containerId = DomUtil.identify(dropdown);
-                       if (!_dropdowns.has(containerId)) {
-                               button.classList.add('jsDropdownEnabled');
-                               button.addEventListener(WCF_CLICK_EVENT, this._toggle.bind(this));
-                               button.addEventListener('keydown', this._handleKeyDown.bind(this));
-                               
-                               _dropdowns.set(containerId, dropdown);
-                               _menus.set(containerId, menu);
-                               
-                               if (!containerId.match(/^wcf\d+$/)) {
-                                       elData(menu, 'source', containerId);
-                               }
-                               
-                               // prevent page scrolling
-                               if (menu.childElementCount && menu.children[0].classList.contains('scrollableDropdownMenu')) {
-                                       menu = menu.children[0];
-                                       elData(menu, 'scroll-to-active', true);
-                                       
-                                       var menuHeight = null, menuRealHeight = null;
-                                       menu.addEventListener('wheel', function (event) {
-                                               if (menuHeight === null) menuHeight = menu.clientHeight;
-                                               if (menuRealHeight === null) menuRealHeight = menu.scrollHeight;
-                                               
-                                               // negative value: scrolling up
-                                               if (event.deltaY < 0 && menu.scrollTop === 0) {
-                                                       event.preventDefault();
-                                               }
-                                               else if (event.deltaY > 0 && (menu.scrollTop + menuHeight === menuRealHeight)) {
-                                                       event.preventDefault();
-                                               }
-                                       }, { passive: false });
-                               }
-                       }
-                       
-                       elData(button, 'target', containerId);
-                       
-                       if (isLazyInitialization) {
-                               setTimeout(function() {
-                                       elData(button, 'dropdown-lazy-init', (isLazyInitialization instanceof MouseEvent));
-                                       
-                                       Core.triggerEvent(button, WCF_CLICK_EVENT);
-                                       
-                                       setTimeout(function() {
-                                               button.removeAttribute('data-dropdown-lazy-init');
-                                       }, 10);
-                               }, 10);
-                       }
-               },
-               
-               /**
-                * Initializes a remote-controlled dropdown.
-                * 
-                * @param       {Element}       dropdown        dropdown wrapper element
-                * @param       {Element}       menu            menu list element
-                */
-               initFragment: function(dropdown, menu) {
-                       this.setup();
-                       
-                       var containerId = DomUtil.identify(dropdown);
-                       if (_dropdowns.has(containerId)) {
-                               return;
-                       }
-                       
-                       _dropdowns.set(containerId, dropdown);
-                       _menuContainer.appendChild(menu);
-                       
-                       _menus.set(containerId, menu);
-               },
-               
-               /**
-                * Registers a callback for open/close events.
-                * 
-                * @param       {string}                        containerId     dropdown wrapper id
-                * @param       {function(string, string)}      callback
-                */
-               registerCallback: function(containerId, callback) {
-                       _callbacks.add(containerId, callback);
-               },
-               
-               /**
-                * Returns the requested dropdown wrapper element.
-                * 
-                * @return      {Element}       dropdown wrapper element
-                */
-               getDropdown: function(containerId) {
-                       return _dropdowns.get(containerId);
-               },
-               
-               /**
-                * Returns the requested dropdown menu list element.
-                * 
-                * @return      {Element}       menu list element
-                */
-               getDropdownMenu: function(containerId) {
-                       return _menus.get(containerId);
-               },
-               
-               /**
-                * Toggles the requested dropdown between opened and closed.
-                * 
-                * @param       {string}        containerId             dropdown wrapper id
-                * @param       {Element=}      referenceElement        alternative reference element, used for reusable dropdown menus
-                * @param       {boolean=}      disableAutoFocus
-                */
-               toggleDropdown: function(containerId, referenceElement, disableAutoFocus) {
-                       this._toggle(null, containerId, referenceElement, disableAutoFocus);
-               },
-               
-               /**
-                * Calculates and sets the alignment of given dropdown.
-                * 
-                * @param       {Element}       dropdown                dropdown wrapper element
-                * @param       {Element}       dropdownMenu            menu list element
-                * @param       {Element=}      alternateElement        alternative reference element for alignment
-                */
-               setAlignment: function(dropdown, dropdownMenu, alternateElement) {
-                       // check if button belongs to an i18n textarea
-                       var button = elBySel('.dropdownToggle', dropdown), refDimensionsElement;
-                       if (button !== null && button.parentNode.classList.contains('inputAddonTextarea')) {
-                               refDimensionsElement = button;
-                       }
-                       
-                       UiAlignment.set(dropdownMenu, alternateElement || dropdown, {
-                               pointerClassNames: ['dropdownArrowBottom', 'dropdownArrowRight'],
-                               refDimensionsElement: refDimensionsElement || null,
-                               
-                               // alignment
-                               horizontal: (elData(dropdownMenu, 'dropdown-alignment-horizontal') === 'right') ? 'right' : 'left',
-                               vertical: (elData(dropdownMenu, 'dropdown-alignment-vertical') === 'top') ? 'top' : 'bottom',
-                               
-                               allowFlip: elData(dropdownMenu, 'dropdown-allow-flip') || 'both'
-                       });
-               },
-               
-               /**
-                * Calculates and sets the alignment of the dropdown identified by given id.
-                * 
-                * @param       {string}        containerId     dropdown wrapper id
-                */
-               setAlignmentById: function(containerId) {
-                       var dropdown = _dropdowns.get(containerId);
-                       if (dropdown === undefined) {
-                               throw new Error("Unknown dropdown identifier '" + containerId + "'.");
-                       }
-                       
-                       var menu = _menus.get(containerId);
-                       
-                       this.setAlignment(dropdown, menu);
-               },
-               
-               /**
-                * Returns true if target dropdown exists and is open.
-                * 
-                * @param       {string}        containerId     dropdown wrapper id
-                * @return      {boolean}       true if dropdown exists and is open
-                */
-               isOpen: function(containerId) {
-                       var menu = _menus.get(containerId);
-                       return (menu !== undefined && menu.classList.contains('dropdownOpen'));
-               },
-               
-               /**
-                * Opens the dropdown unless it is already open.
-                * 
-                * @param       {string}        containerId     dropdown wrapper id
-                * @param       {boolean=}      disableAutoFocus
-                */
-               open: function(containerId, disableAutoFocus) {
-                       var menu = _menus.get(containerId);
-                       if (menu !== undefined && !menu.classList.contains('dropdownOpen')) {
-                               this.toggleDropdown(containerId, undefined, disableAutoFocus);
-                       }
-               },
-               
-               /**
-                * Closes the dropdown identified by given id without notifying callbacks.
-                * 
-                * @param       {string}        containerId     dropdown wrapper id
-                */
-               close: function(containerId) {
-                       var dropdown = _dropdowns.get(containerId);
-                       if (dropdown !== undefined) {
-                               dropdown.classList.remove('dropdownOpen');
-                               _menus.get(containerId).classList.remove('dropdownOpen');
-                       }
-               },
-               
-               /**
-                * Closes all dropdowns.
-                */
-               closeAll: function() {
-                       _dropdowns.forEach((function(dropdown, containerId) {
-                               if (dropdown.classList.contains('dropdownOpen')) {
-                                       dropdown.classList.remove('dropdownOpen');
-                                       _menus.get(containerId).classList.remove('dropdownOpen');
-                                       
-                                       this._notifyCallbacks(containerId, 'close');
-                               }
-                       }).bind(this));
-               },
-               
-               /**
-                * Destroys a dropdown identified by given id.
-                * 
-                * @param       {string}        containerId     dropdown wrapper id
-                * @return      {boolean}       false for unknown dropdowns
-                */
-               destroy: function(containerId) {
-                       if (!_dropdowns.has(containerId)) {
-                               return false;
-                       }
-                       
-                       try {
-                               this.close(containerId);
-                               
-                               elRemove(_menus.get(containerId));
-                       }
-                       catch (e) {
-                               // the elements might not exist anymore thus ignore all errors while cleaning up
-                       }
-                       
-                       _menus.delete(containerId);
-                       _dropdowns.delete(containerId);
-                       
-                       return true;
-               },
-               
-               /**
-                * Handles dropdown positions in overlays when scrolling in the overlay.
-                * 
-                * @param       {Event}         event   event object
-                */
-               _onDialogScroll: function(event) {
-                       var dialogContent = event.currentTarget;
-                       //noinspection JSCheckFunctionSignatures
-                       var dropdowns = elBySelAll('.dropdown.dropdownOpen', dialogContent);
-                       
-                       for (var i = 0, length = dropdowns.length; i < length; i++) {
-                               var dropdown = dropdowns[i];
-                               var containerId = DomUtil.identify(dropdown);
-                               var offset = DomUtil.offset(dropdown);
-                               var dialogOffset = DomUtil.offset(dialogContent);
-                               
-                               // check if dropdown toggle is still (partially) visible
-                               if (offset.top + dropdown.clientHeight <= dialogOffset.top) {
-                                       // top check
-                                       this.toggleDropdown(containerId);
-                               }
-                               else if (offset.top >= dialogOffset.top + dialogContent.offsetHeight) {
-                                       // bottom check
-                                       this.toggleDropdown(containerId);
-                               }
-                               else if (offset.left <= dialogOffset.left) {
-                                       // left check
-                                       this.toggleDropdown(containerId);
-                               }
-                               else if (offset.left >= dialogOffset.left + dialogContent.offsetWidth) {
-                                       // right check
-                                       this.toggleDropdown(containerId);
-                               }
-                               else {
-                                       this.setAlignment(_dropdowns.get(containerId), _menus.get(containerId));
-                               }
-                       }
-               },
-               
-               /**
-                * Recalculates dropdown positions on page scroll.
-                */
-               _onScroll: function() {
-                       _dropdowns.forEach((function(dropdown, containerId) {
-                               if (dropdown.classList.contains('dropdownOpen')) {
-                                       if (elDataBool(dropdown, 'is-overlay-dropdown-button')) {
-                                               this.setAlignment(dropdown, _menus.get(containerId));
-                                       }
-                                       else {
-                                               var menu = _menus.get(dropdown.id);
-                                               if (!elDataBool(menu, 'dropdown-ignore-page-scroll')) {
-                                                       this.close(containerId);
-                                               }
-                                       }
-                               }
-                       }).bind(this));
-               },
-               
-               /**
-                * Notifies callbacks on status change.
-                * 
-                * @param       {string}        containerId     dropdown wrapper id
-                * @param       {string}        action          can be either 'open' or 'close'
-                */
-               _notifyCallbacks: function(containerId, action) {
-                       _callbacks.forEach(containerId, function(callback) {
-                               callback(containerId, action);
-                       });
-               },
-               
-               /**
-                * Toggles the dropdown's state between open and close.
-                * 
-                * @param       {?Event}        event                   event object, should be 'null' if targetId is given
-                * @param       {string?}       targetId                dropdown wrapper id
-                * @param       {Element=}      alternateElement        alternative reference element for alignment
-                * @param       {boolean=}      disableAutoFocus
-                * @return      {boolean}       'false' if event is not null
-                */
-               _toggle: function(event, targetId, alternateElement, disableAutoFocus) {
-                       if (event !== null) {
-                               event.preventDefault();
-                               event.stopPropagation();
-                               
-                               //noinspection JSCheckFunctionSignatures
-                               targetId = elData(event.currentTarget, 'target');
-                               
-                               if (disableAutoFocus === undefined && event instanceof MouseEvent) {
-                                       disableAutoFocus = true;
-                               }
-                       }
-                       
-                       var dropdown = _dropdowns.get(targetId), preventToggle = false;
-                       if (dropdown !== undefined) {
-                               var button;
-                               
-                               // check if the dropdown is still the same, as some components (e.g. page actions)
-                               // re-create the parent of a button
-                               if (event) {
-                                       button = event.currentTarget, parent = button.parentNode;
-                                       if (parent !== dropdown) {
-                                               parent.classList.add('dropdown');
-                                               parent.id = dropdown.id;
-                                               
-                                               // remove dropdown class and id from old parent
-                                               dropdown.classList.remove('dropdown');
-                                               dropdown.id = '';
-                                               
-                                               dropdown = parent;
-                                               _dropdowns.set(targetId, parent);
-                                       }
-                               }
-                               
-                               if (disableAutoFocus === undefined) {
-                                       button = dropdown.closest('.dropdownToggle');
-                                       if (!button) {
-                                               button = elBySel('.dropdownToggle', dropdown);
-                                               
-                                               if (!button && dropdown.id) {
-                                                       button = elBySel('[data-target="' + dropdown.id + '"]');
-                                               }
-                                       }
-                                       
-                                       if (button && elDataBool(button, 'dropdown-lazy-init')) {
-                                               disableAutoFocus = true;
-                                       }
-                               }
-                               
-                               // Repeated clicks on the dropdown button will not cause it to close, the only way
-                               // to close it is by clicking somewhere else in the document or on another dropdown
-                               // toggle. This is used with the search bar to prevent the dropdown from closing by
-                               // setting the caret position in the search input field.
-                               if (elDataBool(dropdown, 'dropdown-prevent-toggle') && dropdown.classList.contains('dropdownOpen')) {
-                                       preventToggle = true;
-                               }
-                               
-                               // check if 'isOverlayDropdownButton' is set which indicates that the dropdown toggle is within an overlay
-                               if (elData(dropdown, 'is-overlay-dropdown-button') === '') {
-                                       var dialogContent = DomTraverse.parentByClass(dropdown, 'dialogContent');
-                                       elData(dropdown, 'is-overlay-dropdown-button', (dialogContent !== null));
-                                       
-                                       if (dialogContent !== null) {
-                                               dialogContent.addEventListener('scroll', this._onDialogScroll.bind(this));
-                                       }
-                               }
-                       }
-                       
-                       // close all dropdowns
-                       _activeTargetId = '';
-                       _dropdowns.forEach((function(dropdown, containerId) {
-                               var menu = _menus.get(containerId);
-                               
-                               if (dropdown.classList.contains('dropdownOpen')) {
-                                       if (preventToggle === false) {
-                                               dropdown.classList.remove('dropdownOpen');
-                                               menu.classList.remove('dropdownOpen');
-                                               
-                                               var button = elBySel('.dropdownToggle', dropdown);
-                                               if (button) elAttr(button, 'aria-expanded', false);
-                                               
-                                               this._notifyCallbacks(containerId, 'close');
-                                       }
-                                       else {
-                                               _activeTargetId = targetId;
-                                       }
-                               }
-                               else if (containerId === targetId && menu.childElementCount > 0) {
-                                       _activeTargetId = targetId;
-                                       dropdown.classList.add('dropdownOpen');
-                                       menu.classList.add('dropdownOpen');
-                                       
-                                       var button = elBySel('.dropdownToggle', dropdown);
-                                       if (button) elAttr(button, 'aria-expanded', true);
-                                       
-                                       if (menu.childElementCount && elDataBool(menu.children[0], 'scroll-to-active')) {
-                                               var list = menu.children[0];
-                                               list.removeAttribute('data-scroll-to-active');
-                                               
-                                               var active = null;
-                                               for (var i = 0, length = list.childElementCount; i < length; i++) {
-                                                       if (list.children[i].classList.contains('active')) {
-                                                               active = list.children[i];
-                                                               break;
-                                                       }
-                                               }
-                                               
-                                               if (active) {
-                                                       list.scrollTop = Math.max((active.offsetTop + active.clientHeight) - menu.clientHeight, 0);
-                                               }
-                                       }
-                                       
-                                       var itemList = elBySel('.scrollableDropdownMenu', menu);
-                                       if (itemList !== null) {
-                                               itemList.classList[(itemList.scrollHeight > itemList.clientHeight ? 'add' : 'remove')]('forceScrollbar');
-                                       }
-                                       
-                                       this._notifyCallbacks(containerId, 'open');
-                                       
-                                       var firstListItem = null;
-                                       if (!disableAutoFocus) {
-                                               elAttr(menu, 'role', 'menu');
-                                               elAttr(menu, 'tabindex', -1);
-                                               menu.removeEventListener('keydown', _callbackDropdownMenuKeyDown);
-                                               menu.addEventListener('keydown', _callbackDropdownMenuKeyDown);
-                                               elBySelAll('li', menu, function (listItem) {
-                                                       if (!listItem.clientHeight) return;
-                                                       if (firstListItem === null) firstListItem = listItem;
-                                                       else if (listItem.classList.contains('active')) firstListItem = listItem;
-                                                       
-                                                       elAttr(listItem, 'role', 'menuitem');
-                                                       elAttr(listItem, 'tabindex', -1);
-                                               });
-                                       }
-                                       
-                                       this.setAlignment(dropdown, menu, alternateElement);
-                                       
-                                       if (firstListItem !== null) {
-                                               firstListItem.focus();
-                                       }
-                               }
-                       }).bind(this));
-                       
-                       //noinspection JSDeprecatedSymbols
-                       window.WCF.Dropdown.Interactive.Handler.closeAll();
-                       
-                       return (event === null);
-               },
-               
-               _handleKeyDown: function(event) {
-                       if (EventKey.Enter(event) || EventKey.Space(event)) {
-                               event.preventDefault();
-                               this._toggle(event);
-                       }
-               },
-               
-               _dropdownMenuKeyDown: function(event) {
-                       var button, dropdown;
-                       
-                       var activeItem = document.activeElement;
-                       if (activeItem.nodeName !== 'LI') {
-                               return;
-                       }
-                       
-                       if (EventKey.ArrowDown(event) || EventKey.ArrowUp(event) || EventKey.End(event) || EventKey.Home(event)) {
-                               event.preventDefault();
-                               
-                               var listItems = Array.prototype.slice.call(elBySelAll('li', activeItem.closest('.dropdownMenu')));
-                               if (EventKey.ArrowUp(event) || EventKey.End(event)) {
-                                       listItems.reverse();
-                               }
-                               var newActiveItem = null;
-                               var isValidItem = function(listItem) {
-                                       return !listItem.classList.contains('dropdownDivider') && listItem.clientHeight > 0;
-                               };
-                               
-                               var activeIndex = listItems.indexOf(activeItem);
-                               if (EventKey.End(event) || EventKey.Home(event)) {
-                                       activeIndex = -1;
-                               }
-                               
-                               for (var i = activeIndex + 1; i < listItems.length; i++) {
-                                       if (isValidItem(listItems[i])) {
-                                               newActiveItem = listItems[i];
-                                               break;
-                                       }
-                               }
-                               
-                               if (newActiveItem === null) {
-                                       for (i = 0; i < listItems.length; i++) {
-                                               if (isValidItem(listItems[i])) {
-                                                       newActiveItem = listItems[i];
-                                                       break;
-                                               }
-                                       }
-                               }
-                               
-                               newActiveItem.focus();
-                       }
-                       else if (EventKey.Enter(event) || EventKey.Space(event)) {
-                               event.preventDefault();
-                               
-                               var target = activeItem;
-                               if (target.childElementCount === 1 && (target.children[0].nodeName === 'SPAN' || target.children[0].nodeName === 'A')) {
-                                       target = target.children[0];
-                               }
-                               
-                               dropdown = _dropdowns.get(_activeTargetId);
-                               button = elBySel('.dropdownToggle', dropdown);
-                               require(['Core'], function(Core) {
-                                       var mouseEvent = elData(dropdown, 'a11y-mouse-event') || 'click';
-                                       Core.triggerEvent(target, mouseEvent);
-                                       
-                                       if (button) button.focus();
-                               });
-                       }
-                       else if (EventKey.Escape(event) || EventKey.Tab(event)) {
-                               event.preventDefault();
-                               
-                               dropdown = _dropdowns.get(_activeTargetId);
-                               button = elBySel('.dropdownToggle', dropdown);
-                               // Remote controlled drop-down menus may not have a dedicated toggle button, instead the
-                               // `dropdown` element itself is the button.
-                               if (button === null && !dropdown.classList.contains('dropdown')) {
-                                       button = dropdown;
-                               }
-                               
-                               this._toggle(null, _activeTargetId);
-                               if (button) button.focus();
-                       }
-               }
-       };
-});
-
-/**
- * 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     WoltLabSuite/Core/Devtools
- */
-define('WoltLabSuite/Core/Devtools',[], 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;
-});
-
-/**
- * Versatile event system similar to the WCF-PHP counter part.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Event/Handler
- */
-define('WoltLabSuite/Core/Event/Handler',['Core', 'Devtools', 'Dictionary'], function(Core, Devtools, Dictionary) {
-       "use strict";
-       
-       var _listeners = new Dictionary();
-       
-       /**
-        * @exports     WoltLabSuite/Core/Event/Handler
-        */
-       return {
-               /**
-                * Adds an event listener.
-                * 
-                * @param       {string}                identifier      event identifier
-                * @param       {string}                action          action name
-                * @param       {function(object)}      callback        callback function
-                * @return      {string}        uuid required for listener removal
-                */
-               add: function(identifier, action, callback) {
-                       if (typeof callback !== 'function') {
-                               throw new TypeError("[WoltLabSuite/Core/Event/Handler] Expected a valid callback for '" + action + "@" + identifier + "'.");
-                       }
-                       
-                       var actions = _listeners.get(identifier);
-                       if (actions === undefined) {
-                               actions = new Dictionary();
-                               _listeners.set(identifier, actions);
-                       }
-                       
-                       var callbacks = actions.get(action);
-                       if (callbacks === undefined) {
-                               callbacks = new Dictionary();
-                               actions.set(action, callbacks);
-                       }
-                       
-                       var uuid = Core.getUuid();
-                       callbacks.set(uuid, callback);
-                       
-                       return uuid;
-               },
-               
-               /**
-                * Fires an event and notifies all listeners.
-                * 
-                * @param       {string}        identifier      event identifier
-                * @param       {string}        action          action name
-                * @param       {object=}       data            event data
-                */
-               fire: function(identifier, action, data) {
-                       Devtools._internal_.eventLog(identifier, action);
-                       
-                       data = data || {};
-                       
-                       var actions = _listeners.get(identifier);
-                       if (actions !== undefined) {
-                               var callbacks = actions.get(action);
-                               if (callbacks !== undefined) {
-                                       callbacks.forEach(function(callback) {
-                                               callback(data);
-                                       });
-                               }
-                       }
-               },
-               
-               /**
-                * Removes an event listener, requires the uuid returned by add().
-                * 
-                * @param       {string}        identifier      event identifier
-                * @param       {string}        action          action name
-                * @param       {string}        uuid            listener uuid
-                */
-               remove: function(identifier, action, uuid) {
-                       var actions = _listeners.get(identifier);
-                       if (actions === undefined) {
-                               return;
-                       }
-                       
-                       var callbacks = actions.get(action);
-                       if (callbacks === undefined) {
-                               return;
-                       }
-                       
-                       callbacks['delete'](uuid);
-               },
-               
-               /**
-                * Removes all event listeners for given action. Omitting the second parameter will
-                * remove all listeners for this identifier.
-                * 
-                * @param       {string}        identifier      event identifier
-                * @param       {string=}       action          action name
-                */
-               removeAll: function(identifier, action) {
-                       if (typeof action !== 'string') action = undefined;
-                       
-                       var actions = _listeners.get(identifier);
-                       if (actions === undefined) {
-                               return;
-                       }
-                       
-                       if (typeof action === 'undefined') {
-                               _listeners['delete'](identifier);
-                       }
-                       else {
-                               actions['delete'](action);
-                       }
-               },
-               
-               /**
-                * Removes all listeners registered for an identifier and ending with a special suffix.
-                * This is commonly used to unbound event handlers for the editor.
-                * 
-                * @param       {string}        identifier      event identifier
-                * @param       {string}        suffix          action suffix
-                */
-               removeAllBySuffix: function (identifier, suffix) {
-                       var actions = _listeners.get(identifier);
-                       if (actions === undefined) {
-                               return;
-                       }
-                       
-                       suffix = '_' + suffix;
-                       var length = suffix.length * -1;
-                       actions.forEach((function (callbacks, action) {
-                               //noinspection JSUnresolvedFunction
-                               if (action.substr(length) === suffix) {
-                                       this.removeAll(identifier, action);
-                               }
-                       }).bind(this));
-               }
-       };
-});
-
-/**
- * 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     WoltLabSuite/Core/List
- */
-define('WoltLabSuite/Core/List',[], 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;
-});
-
-/**
- * Modal dialog handler.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Dialog
- */
-define(
-       'WoltLabSuite/Core/Ui/Dialog',[
-               'Ajax',         'Core',       'Dictionary',
-               'Environment',  'Language',   'ObjectMap', 'Dom/ChangeListener',
-               'Dom/Traverse', 'Dom/Util',   'Ui/Confirmation', 'Ui/Screen', 'Ui/SimpleDropdown',
-               'EventHandler', 'List',       'EventKey'
-       ],
-       function(
-               Ajax,           Core,         Dictionary,
-               Environment,    Language,     ObjectMap,   DomChangeListener,
-               DomTraverse,    DomUtil,      UiConfirmation, UiScreen, UiSimpleDropdown,
-               EventHandler,   List,         EventKey
-       )
-{
-       "use strict";
-       
-       var _activeDialog = null;
-       var _callbackFocus = null;
-       var _container = null;
-       var _dialogs = new Dictionary();
-       var _dialogFullHeight = false;
-       var _dialogObjects = new ObjectMap();
-       var _dialogToObject = new Dictionary();
-       var _focusedBeforeDialog = null;
-       var _keyupListener = null;
-       var _staticDialogs = elByClass('jsStaticDialog');
-       var _validCallbacks = ['onBeforeClose', 'onClose', 'onShow'];
-       
-       // list of supported `input[type]` values for dialog submit
-       var _validInputTypes = ['number', 'password', 'search', 'tel', 'text', 'url'];
-       
-       var _focusableElements = [
-               'a[href]:not([tabindex^="-"]):not([inert])',
-               'area[href]:not([tabindex^="-"]):not([inert])',
-               'input:not([disabled]):not([inert])',
-               'select:not([disabled]):not([inert])',
-               'textarea:not([disabled]):not([inert])',
-               'button:not([disabled]):not([inert])',
-               'iframe:not([tabindex^="-"]):not([inert])',
-               'audio:not([tabindex^="-"]):not([inert])',
-               'video:not([tabindex^="-"]):not([inert])',
-               '[contenteditable]:not([tabindex^="-"]):not([inert])',
-               '[tabindex]:not([tabindex^="-"]):not([inert])'
-       ];
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Dialog
-        */
-       return {
-               /**
-                * Sets up global container and internal variables.
-                */
-               setup: function() {
-                       // Fetch Ajax, as it cannot be provided because of a circular dependency
-                       if (Ajax === undefined) Ajax = require('Ajax');
-                       
-                       _container = elCreate('div');
-                       _container.classList.add('dialogOverlay');
-                       elAttr(_container, 'aria-hidden', 'true');
-                       _container.addEventListener('mousedown', this._closeOnBackdrop.bind(this));
-                       _container.addEventListener('wheel', function (event) {
-                               if (event.target === _container) {
-                                       event.preventDefault();
-                               }
-                       }, { passive: false });
-                       
-                       elById('content').appendChild(_container);
-                       
-                       _keyupListener = (function(event) {
-                               if (event.keyCode === 27) {
-                                       if (event.target.nodeName !== 'INPUT' && event.target.nodeName !== 'TEXTAREA') {
-                                               this.close(_activeDialog);
-                                               
-                                               return false;
-                                       }
-                               }
-                               
-                               return true;
-                       }).bind(this);
-                       
-                       UiScreen.on('screen-xs', {
-                               match: function() { _dialogFullHeight = true; },
-                               unmatch: function() { _dialogFullHeight = false; },
-                               setup: function() { _dialogFullHeight = true; }
-                       });
-                       
-                       this._initStaticDialogs();
-                       DomChangeListener.add('Ui/Dialog', this._initStaticDialogs.bind(this));
-                       
-                       UiScreen.setDialogContainer(_container);
-                       
-                       // mobile safari dynamically shows/hides the bottom browser bar
-                       // causing the window height to differ significantly
-                       if (Environment.platform() === 'ios') {
-                               window.addEventListener('resize', (function () {
-                                       _dialogs.forEach((function (dialog) {
-                                               if (!elAttrBool(dialog.dialog, 'aria-hidden')) {
-                                                       this.rebuild(elData(dialog.dialog, 'id'));
-                                               }
-                                       }).bind(this));
-                               }).bind(this));
-                       }
-               },
-               
-               _initStaticDialogs: function() {
-                       var button, container, id;
-                       while (_staticDialogs.length) {
-                               button = _staticDialogs[0];
-                               button.classList.remove('jsStaticDialog');
-                               
-                               id = elData(button, 'dialog-id');
-                               if (id && (container = elById(id))) {
-                                       ((function(button, container) {
-                                               container.classList.remove('jsStaticDialogContent');
-                                               elData(container, 'is-static-dialog', true);
-                                               elHide(container);
-                                               button.addEventListener(WCF_CLICK_EVENT, (function(event) {
-                                                       event.preventDefault();
-                                                       
-                                                       this.openStatic(container.id, null, { title: elData(container, 'title') });
-                                               }).bind(this));
-                                       }).bind(this))(button, container);
-                               }
-                       }
-               },
-               
-               /**
-                * Opens the dialog and implicitly creates it on first usage.
-                * 
-                * @param       {object}                        callbackObject  used to invoke `_dialogSetup()` on first call
-                * @param       {(string|DocumentFragment=}     html            html content or document fragment to use for dialog content
-                * @returns     {object<string, *>}             dialog data
-                */
-               open: function(callbackObject, html) {
-                       var dialogData = _dialogObjects.get(callbackObject);
-                       if (Core.isPlainObject(dialogData)) {
-                               // dialog already exists
-                               return this.openStatic(dialogData.id, html);
-                       }
-                       
-                       // initialize a new dialog
-                       if (typeof callbackObject._dialogSetup !== 'function') {
-                               throw new Error("Callback object does not implement the method '_dialogSetup()'.");
-                       }
-                       
-                       var setupData = callbackObject._dialogSetup();
-                       if (!Core.isPlainObject(setupData)) {
-                               throw new Error("Expected an object literal as return value of '_dialogSetup()'.");
-                       }
-                       
-                       dialogData = { id: setupData.id };
-                       
-                       var createOnly = true;
-                       if (setupData.source === undefined) {
-                               var dialogElement = elById(setupData.id);
-                               if (dialogElement === null) {
-                                       throw new Error("Element id '" + setupData.id + "' is invalid and no source attribute was given. If you want to use the `html` argument instead, please add `source: null` to your dialog configuration.");
-                               }
-                               
-                               setupData.source = document.createDocumentFragment();
-                               setupData.source.appendChild(dialogElement);
-                               
-                               // remove id and `display: none` from dialog element
-                               dialogElement.removeAttribute('id');
-                               elShow(dialogElement);
-                       }
-                       else if (setupData.source === null) {
-                               // `null` means there is no static markup and `html` should be used instead
-                               setupData.source = html;
-                       }
-                       
-                       else if (typeof setupData.source === 'function') {
-                               setupData.source();
-                       }
-                       else if (Core.isPlainObject(setupData.source)) {
-                               if (typeof html === 'string' && html.trim() !== '') {
-                                       setupData.source = html;
-                               }
-                               else {
-                                       Ajax.api(this, setupData.source.data, (function (data) {
-                                               if (data.returnValues && typeof data.returnValues.template === 'string') {
-                                                       this.open(callbackObject, data.returnValues.template);
-                                                       
-                                                       if (typeof setupData.source.after === 'function') {
-                                                               setupData.source.after(_dialogs.get(setupData.id).content, data);
-                                                       }
-                                               }
-                                       }).bind(this));
-                                       
-                                       // deferred initialization
-                                       return {};
-                               }
-                       }
-                       else {
-                               if (typeof setupData.source === 'string') {
-                                       var dialogElement = elCreate('div');
-                                       elAttr(dialogElement, 'id', setupData.id);
-                                       DomUtil.setInnerHtml(dialogElement, setupData.source);
-                                       
-                                       setupData.source = document.createDocumentFragment();
-                                       setupData.source.appendChild(dialogElement);
-                               }
-                               
-                               if (!setupData.source.nodeType || setupData.source.nodeType !== Node.DOCUMENT_FRAGMENT_NODE) {
-                                       throw new Error("Expected at least a document fragment as 'source' attribute.");
-                               }
-                               
-                               createOnly = false;
-                       }
-                       
-                       _dialogObjects.set(callbackObject, dialogData);
-                       _dialogToObject.set(setupData.id, callbackObject);
-                       
-                       return this.openStatic(setupData.id, setupData.source, setupData.options, createOnly);
-               },
-               
-               /**
-                * Opens an dialog, if the dialog is already open the content container
-                * will be replaced by the HTML string contained in the parameter html.
-                * 
-                * If id is an existing element id, html will be ignored and the referenced
-                * element will be appended to the content element instead.
-                * 
-                * @param       {string}                        id              element id, if exists the html parameter is ignored in favor of the existing element
-                * @param       {?(string|DocumentFragment)}    html            content html
-                * @param       {object<string, *>}             options         list of options, is completely ignored if the dialog already exists
-                * @param       {boolean=}                      createOnly      create the dialog but do not open it
-                * @return      {object<string, *>}             dialog data
-                */
-               openStatic: function(id, html, options, createOnly) {
-                       UiScreen.pageOverlayOpen();
-                       
-                       if (Environment.platform() !== 'desktop') {
-                               if (!this.isOpen(id)) {
-                                       UiScreen.scrollDisable();
-                               }
-                       }
-                       
-                       if (_dialogs.has(id)) {
-                               this._updateDialog(id, html);
-                       }
-                       else {
-                               options = Core.extend({
-                                       backdropCloseOnClick: true,
-                                       closable: true,
-                                       closeButtonLabel: Language.get('wcf.global.button.close'),
-                                       closeConfirmMessage: '',
-                                       disableContentPadding: false,
-                                       title: '',
-                                       
-                                       // callbacks
-                                       onBeforeClose: null,
-                                       onClose: null,
-                                       onShow: null
-                               }, options);
-                               
-                               if (!options.closable) options.backdropCloseOnClick = false;
-                               if (options.closeConfirmMessage) {
-                                       options.onBeforeClose = (function(id) {
-                                               UiConfirmation.show({
-                                                       confirm: this.close.bind(this, id),
-                                                       message: options.closeConfirmMessage
-                                               });
-                                       }).bind(this);
-                               }
-                               
-                               this._createDialog(id, html, options);
-                       }
-                       
-                       var data = _dialogs.get(id);
-                       
-                       // iOS breaks `position: fixed` when input elements or `contenteditable`
-                       // are focused, this will freeze the screen and force Safari to scroll
-                       // to the input field
-                       if (Environment.platform() === 'ios') {
-                               window.setTimeout((function () {
-                                       var input = elBySel('input, textarea', data.content);
-                                       if (input !== null) {
-                                               input.focus();
-                                       }
-                               }).bind(this), 200);
-                       }
-                       
-                       return data;
-               },
-               
-               /**
-                * Sets the dialog title.
-                * 
-                * @param       {(string|object)}       id              element id
-                * @param       {string}                title           dialog title
-                */
-               setTitle: function(id, title) {
-                       id = this._getDialogId(id);
-                       
-                       var data = _dialogs.get(id);
-                       if (data === undefined) {
-                               throw new Error("Expected a valid dialog id, '" + id + "' does not match any active dialog.");
-                       }
-                       
-                       var dialogTitle = elByClass('dialogTitle', data.dialog);
-                       if (dialogTitle.length) {
-                               dialogTitle[0].textContent = title;
-                       }
-               },
-               
-               /**
-                * Sets a callback function on runtime.
-                * 
-                * @param       {(string|object)}       id              element id
-                * @param       {string}                key             callback identifier
-                * @param       {?function}             value           callback function or `null`
-                */
-               setCallback: function(id, key, value) {
-                       if (typeof id === 'object') {
-                               var dialogData = _dialogObjects.get(id);
-                               if (dialogData !== undefined) {
-                                       id = dialogData.id;
-                               }
-                       }
-                       
-                       var data = _dialogs.get(id);
-                       if (data === undefined) {
-                               throw new Error("Expected a valid dialog id, '" + id + "' does not match any active dialog.");
-                       }
-                       
-                       if (_validCallbacks.indexOf(key) === -1) {
-                               throw new Error("Invalid callback identifier, '" + key + "' is not recognized.");
-                       }
-                       
-                       if (typeof value !== 'function' && value !== null) {
-                               throw new Error("Only functions or the 'null' value are acceptable callback values ('" + typeof value+ "' given).");
-                       }
-                       
-                       data[key] = value;
-               },
-               
-               /**
-                * Creates the DOM for a new dialog and opens it.
-                * 
-                * @param       {string}                        id              element id, if exists the html parameter is ignored in favor of the existing element
-                * @param       {?(string|DocumentFragment)}    html            content html
-                * @param       {object<string, *>}             options         list of options
-                * @param       {boolean=}                      createOnly      create the dialog but do not open it
-                */
-               _createDialog: function(id, html, options, createOnly) {
-                       var element = null;
-                       if (html === null) {
-                               element = elById(id);
-                               if (element === null) {
-                                       throw new Error("Expected either a HTML string or an existing element id.");
-                               }
-                       }
-                       
-                       var dialog = elCreate('div');
-                       dialog.classList.add('dialogContainer');
-                       elAttr(dialog, 'aria-hidden', 'true');
-                       elAttr(dialog, 'role', 'dialog');
-                       elData(dialog, 'id', id);
-                       
-                       var header = elCreate('header');
-                       dialog.appendChild(header);
-                       
-                       var titleId = DomUtil.getUniqueId();
-                       elAttr(dialog, 'aria-labelledby', titleId);
-                       
-                       var title = elCreate('span');
-                       title.classList.add('dialogTitle');
-                       title.textContent = options.title;
-                       elAttr(title, 'id', titleId);
-                       header.appendChild(title);
-                       
-                       if (options.closable) {
-                               var closeButton = elCreate('a');
-                               closeButton.className = 'dialogCloseButton jsTooltip';
-                               elAttr(closeButton, 'role', 'button');
-                               elAttr(closeButton, 'tabindex', '0');
-                               elAttr(closeButton, 'title', options.closeButtonLabel);
-                               elAttr(closeButton, 'aria-label', options.closeButtonLabel);
-                               closeButton.addEventListener(WCF_CLICK_EVENT, this._close.bind(this));
-                               header.appendChild(closeButton);
-                               
-                               var span = elCreate('span');
-                               span.className = 'icon icon24 fa-times';
-                               closeButton.appendChild(span);
-                       }
-                       
-                       var contentContainer = elCreate('div');
-                       contentContainer.classList.add('dialogContent');
-                       if (options.disableContentPadding) contentContainer.classList.add('dialogContentNoPadding');
-                       dialog.appendChild(contentContainer);
-                       
-                       contentContainer.addEventListener('wheel', function (event) {
-                               var allowScroll = false;
-                               var element = event.target, clientHeight, scrollHeight, scrollTop;
-                               while (true) {
-                                       clientHeight = element.clientHeight;
-                                       scrollHeight = element.scrollHeight;
-                                       
-                                       if (clientHeight < scrollHeight) {
-                                               scrollTop = element.scrollTop;
-                                               
-                                               // negative value: scrolling up
-                                               if (event.deltaY < 0 && scrollTop > 0) {
-                                                       allowScroll = true;
-                                                       break;
-                                               }
-                                               else if (event.deltaY > 0 && (scrollTop + clientHeight < scrollHeight)) {
-                                                       allowScroll = true;
-                                                       break;
-                                               }
-                                       }
-                                       
-                                       if (!element || element === contentContainer) {
-                                               break;
-                                       }
-                                       
-                                       element = element.parentNode;
-                               }
-                               
-                               if (allowScroll === false) {
-                                       event.preventDefault();
-                               }
-                       }, { passive: false });
-                       
-                       var content;
-                       if (element === null) {
-                               if (typeof html === 'string') {
-                                       content = elCreate('div');
-                                       content.id = id;
-                                       DomUtil.setInnerHtml(content, html);
-                               }
-                               else if (html instanceof DocumentFragment) {
-                                       var children = [], node;
-                                       for (var i = 0, length = html.childNodes.length; i < length; i++) {
-                                               node = html.childNodes[i];
-                                               
-                                               if (node.nodeType === Node.ELEMENT_NODE) {
-                                                       children.push(node);
-                                               }
-                                       }
-                                       
-                                       if (children[0].nodeName !== 'DIV' || children.length > 1) {
-                                               content = elCreate('div');
-                                               content.id = id;
-                                               content.appendChild(html);
-                                       }
-                                       else {
-                                               content = children[0];
-                                       }
-                               }
-                               else {
-                                       throw new TypeError("'html' must either be a string or a DocumentFragment");
-                               }
-                       }
-                       else {
-                               content = element;
-                       }
-                       
-                       contentContainer.appendChild(content);
-                       
-                       if (content.style.getPropertyValue('display') === 'none') {
-                               elShow(content);
-                       }
-                       
-                       _dialogs.set(id, {
-                               backdropCloseOnClick: options.backdropCloseOnClick,
-                               closable: options.closable,
-                               content: content,
-                               dialog: dialog,
-                               header: header,
-                               onBeforeClose: options.onBeforeClose,
-                               onClose: options.onClose,
-                               onShow: options.onShow,
-                               
-                               submitButton: null,
-                               inputFields: new List()
-                       });
-                       
-                       DomUtil.prepend(dialog, _container);
-                       
-                       if (typeof options.onSetup === 'function') {
-                               options.onSetup(content);
-                       }
-                       
-                       if (createOnly !== true) {
-                               this._updateDialog(id, null);
-                       }
-               },
-               
-               /**
-                * Updates the dialog's content element.
-                * 
-                * @param       {string}                id              element id
-                * @param       {?string}               html            content html, prevent changes by passing null
-                */
-               _updateDialog: function(id, html) {
-                       var data = _dialogs.get(id);
-                       if (data === undefined) {
-                               throw new Error("Expected a valid dialog id, '" + id + "' does not match any active dialog.");
-                       }
-                       
-                       if (typeof html === 'string') {
-                               DomUtil.setInnerHtml(data.content, html);
-                       }
-                       
-                       if (elAttr(data.dialog, 'aria-hidden') === 'true') {
-                               if (_callbackFocus === null) {
-                                       _callbackFocus = this._maintainFocus.bind(this);
-                                       document.body.addEventListener('focus', _callbackFocus, { capture: true });
-                               }
-                               
-                               if (data.closable && elAttr(_container, 'aria-hidden') === 'true') {
-                                       window.addEventListener('keyup', _keyupListener);
-                               }
-                               
-                               elAttr(data.dialog, 'aria-hidden', 'false');
-                               elAttr(_container, 'aria-hidden', 'false');
-                               elData(_container, 'close-on-click', (data.backdropCloseOnClick ? 'true' : 'false'));
-                               _activeDialog = id;
-                               
-                               // Keep a reference to the currently focused element to be able to restore it later.
-                               _focusedBeforeDialog = document.activeElement;
-                               
-                               // Set the focus to the first focusable child of the dialog element.
-                               var closeButton = elBySel('.dialogCloseButton', data.header);
-                               if (closeButton) elAttr(closeButton, 'inert', true);
-                               this._setFocusToFirstItem(data.dialog);
-                               if (closeButton) closeButton.removeAttribute('inert');
-                               
-                               if (typeof data.onShow === 'function') {
-                                       data.onShow(data.content);
-                               }
-                               
-                               if (elDataBool(data.content, 'is-static-dialog')) {
-                                       EventHandler.fire('com.woltlab.wcf.dialog', 'openStatic', {
-                                               content: data.content,
-                                               id: id
-                                       });
-                               }
-                               
-                               // close existing dropdowns
-                               UiSimpleDropdown.closeAll();
-                               window.WCF.Dropdown.Interactive.Handler.closeAll();
-                       }
-                       
-                       this.rebuild(id);
-                       
-                       DomChangeListener.trigger();
-               },
-               
-               /**
-                * @param {Event} event
-                */
-               _maintainFocus: function(event) {
-                       if (_activeDialog) {
-                               var data = _dialogs.get(_activeDialog);
-                               if (!data.dialog.contains(event.target) && !event.target.closest('.dropdownMenuContainer') && !event.target.closest('.datePicker')) {
-                                       this._setFocusToFirstItem(data.dialog, true);
-                               }
-                       }
-               },
-               
-               /**
-                * @param {Element} dialog
-                * @param {boolean} maintain
-                */
-               _setFocusToFirstItem: function(dialog, maintain) {
-                       var focusElement = this._getFirstFocusableChild(dialog);
-                       if (focusElement !== null) {
-                               if (maintain) {
-                                       if (focusElement.id === 'username' || focusElement.name === 'username') {
-                                               if (Environment.browser() === 'safari' && Environment.platform() === 'ios') {
-                                                       // iOS Safari's username/password autofill breaks if the input field is focused 
-                                                       focusElement = null;
-                                               }
-                                       }
-                               }
-                               
-                               if (focusElement) focusElement.focus();
-                       }
-               },
-               
-               /**
-                * @param {Element} node
-                * @returns {?Element}
-                */
-               _getFirstFocusableChild: function(node) {
-                       var nodeList = elBySelAll(_focusableElements.join(','), node);
-                       for (var i = 0, length = nodeList.length; i < length; i++) {
-                               if (nodeList[i].offsetWidth && nodeList[i].offsetHeight && nodeList[i].getClientRects().length) {
-                                       return nodeList[i];
-                               }
-                       }
-                       
-                       return null;    
-               },
-               
-               /**
-                * Rebuilds dialog identified by given id.
-                * 
-                * @param       {string}        id      element id
-                */
-               rebuild: function(id) {
-                       id = this._getDialogId(id);
-                       
-                       var data = _dialogs.get(id);
-                       if (data === undefined) {
-                               throw new Error("Expected a valid dialog id, '" + id + "' does not match any active dialog.");
-                       }
-                       
-                       // ignore non-active dialogs
-                       if (elAttr(data.dialog, 'aria-hidden') === 'true') {
-                               return;
-                       }
-                       
-                       var contentContainer = data.content.parentNode;
-                       
-                       var formSubmit = elBySel('.formSubmit', data.content);
-                       var unavailableHeight = 0;
-                       if (formSubmit !== null) {
-                               contentContainer.classList.add('dialogForm');
-                               formSubmit.classList.add('dialogFormSubmit');
-                               
-                               unavailableHeight += DomUtil.outerHeight(formSubmit);
-                               
-                               // Calculated height can be a fractional value and depending on the
-                               // browser the results can vary. By subtracting a single pixel we're
-                               // working around fractional values, without visually changing anything.
-                               unavailableHeight -= 1;
-                               
-                               contentContainer.style.setProperty('margin-bottom', unavailableHeight + 'px', '');
-                       }
-                       else {
-                               contentContainer.classList.remove('dialogForm');
-                               contentContainer.style.removeProperty('margin-bottom');
-                       }
-                       
-                       unavailableHeight += DomUtil.outerHeight(data.header);
-                       
-                       var maximumHeight = (window.innerHeight * (_dialogFullHeight ? 1 : 0.8)) - unavailableHeight;
-                       contentContainer.style.setProperty('max-height', ~~maximumHeight + 'px', '');
-                       
-                       // Chrome and Safari use heavy anti-aliasing when the dialog's width
-                       // cannot be evenly divided, causing the whole text to become blurry
-                       if (Environment.browser() === 'chrome' || Environment.browser() === 'safari') {
-                               // `clientWidth` will report an integer value that isn't rounded properly (e.g. 0.59 -> 0)
-                               var floatWidth = parseFloat(window.getComputedStyle(data.content).width);
-                               var needsFix = (Math.round(floatWidth) % 2) !== 0;
-                               
-                               data.content.parentNode.classList[(needsFix ? 'add' : 'remove')]('jsWebKitFractionalPixel');
-                       }
-                       
-                       var callbackObject = _dialogToObject.get(id);
-                       //noinspection JSUnresolvedVariable
-                       if (callbackObject !== undefined && typeof callbackObject._dialogSubmit === 'function') {
-                               var inputFields = elBySelAll('input[data-dialog-submit-on-enter="true"]', data.content);
-                               
-                               var submitButton = elBySel('.formSubmit > input[type="submit"], .formSubmit > button[data-type="submit"]', data.content);
-                               if (submitButton === null) {
-                                       // check if there is at least one input field with submit handling,
-                                       // otherwise we'll assume the dialog has not been populated yet
-                                       if (inputFields.length === 0) {
-                                               console.warn("Broken dialog, expected a submit button.", data.content);
-                                       }
-                                       
-                                       return;
-                               }
-                               
-                               if (data.submitButton !== submitButton) {
-                                       data.submitButton = submitButton;
-                                       
-                                       submitButton.addEventListener(WCF_CLICK_EVENT, (function (event) {
-                                               event.preventDefault();
-                                               
-                                               this._submit(id);
-                                       }).bind(this));
-                                       
-                                       // bind input fields
-                                       var inputField, _callbackKeydown = null;
-                                       for (var i = 0, length = inputFields.length; i < length; i++) {
-                                               inputField = inputFields[i];
-                                               
-                                               if (data.inputFields.has(inputField)) continue;
-                                               
-                                               if (_validInputTypes.indexOf(inputField.type) === -1) {
-                                                       console.warn("Unsupported input type.", inputField);
-                                                       continue;
-                                               }
-                                               
-                                               data.inputFields.add(inputField);
-                                               
-                                               if (_callbackKeydown === null) {
-                                                       _callbackKeydown = (function (event) {
-                                                               if (EventKey.Enter(event)) {
-                                                                       event.preventDefault();
-                                                                       
-                                                                       this._submit(id);
-                                                               }
-                                                       }).bind(this);
-                                               }
-                                               inputField.addEventListener('keydown', _callbackKeydown);
-                                       }
-                               }
-                       }
-               },
-               
-               /**
-                * Submits the dialog.
-                * 
-                * @param       {string}        id      dialog id
-                * @protected
-                */
-               _submit: function (id) {
-                       var data = _dialogs.get(id);
-                       
-                       var isValid = true;
-                       data.inputFields.forEach(function (inputField) {
-                               if (inputField.required) {
-                                       if (inputField.value.trim() === '') {
-                                               elInnerError(inputField, Language.get('wcf.global.form.error.empty'));
-                                               
-                                               isValid = false;
-                                       }
-                                       else {
-                                               elInnerError(inputField, false);
-                                       }
-                               }
-                       });
-                       
-                       if (isValid) {
-                               //noinspection JSUnresolvedFunction
-                               _dialogToObject.get(id)._dialogSubmit();
-                       }
-               },
-               
-               /**
-                * Handles clicks on the close button or the backdrop if enabled.
-                * 
-                * @param       {object}        event           click event
-                * @return      {boolean}       false if the event should be cancelled
-                */
-               _close: function(event) {
-                       event.preventDefault();
-                       
-                       var data = _dialogs.get(_activeDialog);
-                       if (typeof data.onBeforeClose === 'function') {
-                               data.onBeforeClose(_activeDialog);
-                               
-                               return false;
-                       }
-                       
-                       this.close(_activeDialog);
-               },
-               
-               /**
-                * Closes the current active dialog by clicks on the backdrop.
-                * 
-                * @param       {object}        event   event object
-                */
-               _closeOnBackdrop: function(event) {
-                       if (event.target !== _container) {
-                               return true;
-                       }
-                       
-                       if (elData(_container, 'close-on-click') === 'true') {
-                               this._close(event);
-                       }
-                       else {
-                               event.preventDefault();
-                       }
-               },
-               
-               /**
-                * Closes a dialog identified by given id.
-                * 
-                * @param       {(string|object)}       id      element id or callback object
-                */
-               close: function(id) {
-                       id = this._getDialogId(id);
-                       
-                       var data = _dialogs.get(id);
-                       if (data === undefined) {
-                               throw new Error("Expected a valid dialog id, '" + id + "' does not match any active dialog.");
-                       }
-                       
-                       elAttr(data.dialog, 'aria-hidden', 'true');
-                       
-                       // avoid keyboard focus on a now hidden element 
-                       if (document.activeElement.closest('.dialogContainer') === data.dialog) {
-                               document.activeElement.blur();
-                       }
-                       
-                       if (typeof data.onClose === 'function') {
-                               data.onClose(id);
-                       }
-                       
-                       // get next active dialog
-                       _activeDialog = null;
-                       for (var i = 0; i < _container.childElementCount; i++) {
-                               var child = _container.children[i];
-                               if (elAttr(child, 'aria-hidden') === 'false') {
-                                       _activeDialog = elData(child, 'id');
-                                       break;
-                               }
-                       }
-                       
-                       if (_activeDialog === null) {
-                               elAttr(_container, 'aria-hidden', 'true');
-                               elData(_container, 'close-on-click', 'false');
-                               
-                               if (data.closable) {
-                                       window.removeEventListener('keyup', _keyupListener);
-                               }
-                               
-                               UiScreen.pageOverlayClose();
-                       }
-                       else {
-                               data = _dialogs.get(_activeDialog);
-                               elData(_container, 'close-on-click', (data.backdropCloseOnClick ? 'true' : 'false'));
-                       }
-                       
-                       if (Environment.platform() !== 'desktop') {
-                               UiScreen.scrollEnable();
-                       }
-               },
-               
-               /**
-                * Returns the dialog data for given element id.
-                * 
-                * @param       {(string|object)}       id      element id or callback object
-                * @return      {(object|undefined)}    dialog data or undefined if element id is unknown
-                */
-               getDialog: function(id) {
-                       return _dialogs.get(this._getDialogId(id));
-               },
-               
-               /**
-                * Returns true for open dialogs.
-                * 
-                * @param       {(string|object)}       id      element id or callback object
-                * @return      {boolean}
-                */
-               isOpen: function(id) {
-                       var data = this.getDialog(id);
-                       return (data !== undefined && elAttr(data.dialog, 'aria-hidden') === 'false');
-               },
-               
-               /**
-                * Destroys a dialog instance.
-                * 
-                * @param       {Object}        callbackObject  the same object that was used to invoke `_dialogSetup()` on first call
-                */
-               destroy: function(callbackObject) {
-                       if (typeof callbackObject !== 'object' || callbackObject instanceof String) {
-                               throw new TypeError("Expected the callback object as parameter.");
-                       }
-                       
-                       if (_dialogObjects.has(callbackObject)) {
-                               var id = _dialogObjects.get(callbackObject).id;
-                               if (this.isOpen(id)) {
-                                       this.close(id);
-                               }
-                               
-                               _dialogs.delete(id);
-                               _dialogObjects.delete(callbackObject);
-                       }
-               },
-               
-               /**
-                * Returns a dialog's id.
-                * 
-                * @param       {(string|object)}       id      element id or callback object
-                * @return      {string}
-                * @protected
-                */
-               _getDialogId: function(id) {
-                       if (typeof id === 'object') {
-                               var dialogData = _dialogObjects.get(id);
-                               if (dialogData !== undefined) {
-                                       return dialogData.id;
-                               }
-                       }
-                       
-                       return id.toString();
-               },
-               
-               _ajaxSetup: function() {
-                       return {};
-               }
-       };
-});
-
-/**
- * Provides the AJAX status overlay.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ajax/Status
- */
-define('WoltLabSuite/Core/Ajax/Status',['Language'], function(Language) {
-       "use strict";
-       
-       var _activeRequests = 0;
-       var _overlay = null;
-       var _timeoutShow = null;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ajax/Status
-        */
-       var AjaxStatus = {
-               /**
-                * Initializes the status overlay on first usage.
-                */
-               _init: function() {
-                       _overlay = elCreate('div');
-                       _overlay.classList.add('spinner');
-                       elAttr(_overlay, 'role', 'status');
-                       
-                       var icon = elCreate('span');
-                       icon.className = 'icon icon48 fa-spinner';
-                       _overlay.appendChild(icon);
-                       
-                       var title = elCreate('span');
-                       title.textContent = Language.get('wcf.global.loading');
-                       _overlay.appendChild(title);
-                       
-                       document.body.appendChild(_overlay);
-               },
-               
-               /**
-                * Shows the loading overlay.
-                */
-               show: function() {
-                       if (_overlay === null) {
-                               this._init();
-                       }
-                       
-                       _activeRequests++;
-                       
-                       if (_timeoutShow === null) {
-                               _timeoutShow = window.setTimeout(function() {
-                                       if (_activeRequests) {
-                                               _overlay.classList.add('active');
-                                       }
-                                       
-                                       _timeoutShow = null;
-                               }, 250);
-                       }
-               },
-               
-               /**
-                * Hides the loading overlay.
-                */
-               hide: function() {
-                       _activeRequests--;
-                       
-                       if (_activeRequests === 0) {
-                               if (_timeoutShow !== null) {
-                                       window.clearTimeout(_timeoutShow);
-                               }
-                               
-                               _overlay.classList.remove('active');
-                       }
-               }
-       };
-       
-       return AjaxStatus;
-});
-
-/**
- * Versatile AJAX request handling.
- * 
- * In case you want to issue JSONP requests, please use `AjaxJsonp` instead.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ajax/Request
- */
-define('WoltLabSuite/Core/Ajax/Request',['Core', 'Language', 'Dom/ChangeListener', 'Dom/Util', 'Ui/Dialog', 'WoltLabSuite/Core/Ajax/Status'], function(Core, Language, DomChangeListener, DomUtil, UiDialog, AjaxStatus) {
-       "use strict";
-       
-       var _didInit = false;
-       var _ignoreAllErrors = false;
-       
-       /**
-        * @constructor
-        */
-       function AjaxRequest(options) {
-               this._data = null;
-               this._options = {};
-               this._previousXhr = null;
-               this._xhr = null;
-               
-               this._init(options);
-       }
-       AjaxRequest.prototype = {
-               /**
-                * Initializes the request options.
-                * 
-                * @param       {Object}        options         request options
-                */
-               _init: function(options) {
-                       this._options = Core.extend({
-                               // request data
-                               data: {},
-                               contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
-                               responseType: 'application/json',
-                               type: 'POST',
-                               url: '',
-                               withCredentials: false,
-                               
-                               // behavior
-                               autoAbort: false,
-                               ignoreError: false,
-                               pinData: false,
-                               silent: false,
-                               includeRequestedWith: true,
-                               
-                               // callbacks
-                               failure: null,
-                               finalize: null,
-                               success: null,
-                               progress: null,
-                               uploadProgress: null,
-                               
-                               callbackObject: null
-                       }, options);
-                       
-                       if (typeof options.callbackObject === 'object') {
-                               this._options.callbackObject = options.callbackObject;
-                       }
-                       
-                       this._options.url = Core.convertLegacyUrl(this._options.url);
-                       if (this._options.url.indexOf('index.php') === 0) {
-                               this._options.url = WSC_API_URL + this._options.url;
-                       }
-                       
-                       if (this._options.url.indexOf(WSC_API_URL) === 0) {
-                               this._options.includeRequestedWith = true;
-                               // always include credentials when querying the very own server
-                               this._options.withCredentials = true;
-                       }
-                       
-                       if (this._options.pinData) {
-                               this._data = Core.extend({}, this._options.data);
-                       }
-                       
-                       if (this._options.callbackObject !== null) {
-                               if (typeof this._options.callbackObject._ajaxFailure === 'function') this._options.failure = this._options.callbackObject._ajaxFailure.bind(this._options.callbackObject);
-                               if (typeof this._options.callbackObject._ajaxFinalize === 'function') this._options.finalize = this._options.callbackObject._ajaxFinalize.bind(this._options.callbackObject);
-                               if (typeof this._options.callbackObject._ajaxSuccess === 'function') this._options.success = this._options.callbackObject._ajaxSuccess.bind(this._options.callbackObject);
-                               if (typeof this._options.callbackObject._ajaxProgress === 'function') this._options.progress = this._options.callbackObject._ajaxProgress.bind(this._options.callbackObject);
-                               if (typeof this._options.callbackObject._ajaxUploadProgress === 'function') this._options.uploadProgress = this._options.callbackObject._ajaxUploadProgress.bind(this._options.callbackObject);
-                       }
-                       
-                       if (_didInit === false) {
-                               _didInit = true;
-                               
-                               window.addEventListener('beforeunload', function() { _ignoreAllErrors = true; });
-                       }
-               },
-               
-               /**
-                * Dispatches a request, optionally aborting a currently active request.
-                * 
-                * @param       {boolean}       abortPrevious   abort currently active request
-                */
-               sendRequest: function(abortPrevious) {
-                       if (abortPrevious === true || this._options.autoAbort) {
-                               this.abortPrevious();
-                       }
-                       
-                       if (!this._options.silent) {
-                               AjaxStatus.show();
-                       }
-                       
-                       if (this._xhr instanceof XMLHttpRequest) {
-                               this._previousXhr = this._xhr;
-                       }
-                       
-                       this._xhr = new XMLHttpRequest();
-                       this._xhr.open(this._options.type, this._options.url, true);
-                       if (this._options.contentType) {
-                               this._xhr.setRequestHeader('Content-Type', this._options.contentType);
-                       }
-                       if (this._options.withCredentials || this._options.includeRequestedWith) {
-                               this._xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
-                       }
-                       if (this._options.withCredentials) {
-                               this._xhr.withCredentials = true;
-                       }
-                       
-                       var self = this;
-                       var options = Core.clone(this._options);
-                       this._xhr.onload = function() {
-                               if (this.readyState === XMLHttpRequest.DONE) {
-                                       if (this.status >= 200 && this.status < 300 || this.status === 304) {
-                                               if (options.responseType && this.getResponseHeader('Content-Type').indexOf(options.responseType) !== 0) {
-                                                       // request succeeded but invalid response type
-                                                       self._failure(this, options);
-                                               }
-                                               else {
-                                                       self._success(this, options);
-                                               }
-                                       }
-                                       else {
-                                               self._failure(this, options);
-                                       }
-                               }
-                       };
-                       this._xhr.onerror = function() {
-                               self._failure(this, options);
-                       };
-                       
-                       if (this._options.progress) {
-                               this._xhr.onprogress = this._options.progress;
-                       }
-                       if (this._options.uploadProgress) {
-                               this._xhr.upload.onprogress = this._options.uploadProgress;
-                       }
-                       
-                       if (this._options.type === 'POST') {
-                               var data = this._options.data;
-                               if (typeof data === 'object' && Core.getType(data) !== 'FormData') {
-                                       data = Core.serialize(data);
-                               }
-                               
-                               this._xhr.send(data);
-                       }
-                       else {
-                               this._xhr.send();
-                       }
-               },
-               
-               /**
-                * Aborts a previous request.
-                */
-               abortPrevious: function() {
-                       if (this._previousXhr === null) {
-                               return;
-                       }
-                       
-                       this._previousXhr.abort();
-                       this._previousXhr = null;
-                       
-                       if (!this._options.silent) {
-                               AjaxStatus.hide();
-                       }
-               },
-               
-               /**
-                * Sets a specific option.
-                * 
-                * @param       {string}        key     option name
-                * @param       {?}             value   option value
-                */
-               setOption: function(key, value) {
-                       this._options[key] = value;
-               },
-               
-               /**
-                * Returns an option by key or undefined.
-                * 
-                * @param       {string}        key     option name
-                * @return      {(*|null)}      option value or null
-                */
-               getOption: function(key) {
-                       if (objOwns(this._options, key)) {
-                               return this._options[key];
-                       }
-                       
-                       return null;
-               },
-               
-               /**
-                * Sets request data while honoring pinned data from setup callback.
-                * 
-                * @param       {Object}        data    request data
-                */
-               setData: function(data) {
-                       if (this._data !== null && Core.getType(data) !== 'FormData') {
-                               data = Core.extend(this._data, data);
-                       }
-                       
-                       this._options.data = data;
-               },
-               
-               /**
-                * Handles a successful request.
-                * 
-                * @param       {XMLHttpRequest}        xhr             request object
-                * @param       {Object}                options         request options
-                */
-               _success: function(xhr, options) {
-                       if (!options.silent) {
-                               AjaxStatus.hide();
-                       }
-                       
-                       if (typeof options.success === 'function') {
-                               var data = null;
-                               if (xhr.getResponseHeader('Content-Type') === 'application/json') {
-                                       try {
-                                               data = JSON.parse(xhr.responseText);
-                                       }
-                                       catch (e) {
-                                               // invalid JSON
-                                               this._failure(xhr, options);
-                                               
-                                               return;
-                                       }
-                                       
-                                       // trim HTML before processing, see http://jquery.com/upgrade-guide/1.9/#jquery-htmlstring-versus-jquery-selectorstring
-                                       if (data && data.returnValues && data.returnValues.template !== undefined) {
-                                               data.returnValues.template = data.returnValues.template.trim();
-                                       }
-                                       
-                                       // force-invoke the background queue
-                                       if (data && data.forceBackgroundQueuePerform) {
-                                               require(['WoltLabSuite/Core/BackgroundQueue'], function(BackgroundQueue) {
-                                                       BackgroundQueue.invoke();
-                                               });
-                                       }
-                               }
-                               
-                               options.success(data, xhr.responseText, xhr, options.data);
-                       }
-                       
-                       this._finalize(options);
-               },
-               
-               /**
-                * Handles failed requests, this can be both a successful request with
-                * a non-success status code or an entirely failed request.
-                * 
-                * @param       {XMLHttpRequest}        xhr             request object
-                * @param       {Object}                options         request options
-                */
-               _failure: function (xhr, options) {
-                       if (_ignoreAllErrors) {
-                               return;
-                       }
-                       
-                       if (!options.silent) {
-                               AjaxStatus.hide();
-                       }
-                       
-                       var data = null;
-                       try {
-                               data = JSON.parse(xhr.responseText);
-                       }
-                       catch (e) {}
-                       
-                       var showError = true;
-                       if (typeof options.failure === 'function') {
-                               showError = options.failure((data || {}), (xhr.responseText || ''), xhr, options.data);
-                       }
-                       
-                       if (options.ignoreError !== true && showError !== false) {
-                               var html = this.getErrorHtml(data, xhr);
-                               
-                               if (html) {
-                                       if (UiDialog === undefined) UiDialog = require('Ui/Dialog');
-                                       UiDialog.openStatic(DomUtil.getUniqueId(), html, {
-                                               title: Language.get('wcf.global.error.title')
-                                       });
-                               }
-                       }
-                       
-                       this._finalize(options);
-               },
-               
-               /**
-                * Returns the inner HTML for an error/exception display.
-                * 
-                * @param       {Object}                data
-                * @param       {XMLHttpRequest}        xhr
-                * @return      {string}
-                */
-               getErrorHtml: function(data, xhr) {
-                       var details = '';
-                       var message = '';
-                       
-                       if (data !== null) {
-                               if (data.file && data.line) {
-                                       details += '<br><p>File:</p><p>' + data.file + ' in line ' + data.line + '</p>';
-                               }
-                               
-                               if (data.stacktrace) details += '<br><p>Stacktrace:</p><p>' + data.stacktrace + '</p>';
-                               else if (data.exceptionID) details += '<br><p>Exception ID: <code>' + data.exceptionID + '</code></p>';
-                               
-                               message = data.message;
-                               
-                               data.previous.forEach(function(previous) {
-                                       details += '<hr><p>' + previous.message + '</p>';
-                                       details += '<br><p>Stacktrace</p><p>' + previous.stacktrace + '</p>';
-                               });
-                       }
-                       else {
-                               message = xhr.responseText;
-                       }
-                       
-                       if (!message || message === 'undefined') {
-                               if (!ENABLE_DEBUG_MODE && !ENABLE_PRODUCTION_DEBUG_MODE) return null;
-                               
-                               message = 'XMLHttpRequest failed without a responseText. Check your browser console.'
-                       }
-                       
-                       return '<div class="ajaxDebugMessage"><p>' + message + '</p>' + details + '</div>';
-               },
-               
-               /**
-                * Finalizes a request.
-                * 
-                * @param       {Object}        options         request options
-                */
-               _finalize: function(options) {
-                       if (typeof options.finalize === 'function') {
-                               options.finalize(this._xhr);
-                       }
-                       
-                       this._previousXhr = null;
-                       
-                       DomChangeListener.trigger();
-                       
-                       // fix anchor tags generated through WCF::getAnchor()
-                       var links = elBySelAll('a[href*="#"]');
-                       for (var i = 0, length = links.length; i < length; i++) {
-                               var link = links[i];
-                               var href = elAttr(link, 'href');
-                               if (href.indexOf('AJAXProxy') !== -1 || href.indexOf('ajax-proxy') !== -1) {
-                                       href = href.substr(href.indexOf('#'));
-                                       elAttr(link, 'href', document.location.toString().replace(/#.*/, '') + href);
-                               }
-                       }
-               }
-       };
-       
-       return AjaxRequest;
-});
-
-/**
- * Handles AJAX requests.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ajax
- */
-define('WoltLabSuite/Core/Ajax',['AjaxRequest', 'Core', 'ObjectMap'], function(AjaxRequest, Core, ObjectMap) {
-       "use strict";
-       
-       var _requests = new ObjectMap();
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ajax
-        */
-       return {
-               /**
-                * Shorthand function to perform a request against the WCF-API with overrides
-                * for success and failure callbacks.
-                * 
-                * @param       {object}                callbackObject  callback object
-                * @param       {object<string, *>=}    data            request data
-                * @param       {function=}             success         success callback
-                * @param       {function=}             failure         failure callback
-                * @return      {AjaxRequest}
-                */
-               api: function(callbackObject, data, success, failure) {
-                       // Fetch AjaxRequest, as it cannot be provided because of a circular dependency
-                       if (AjaxRequest === undefined) AjaxRequest = require('AjaxRequest');
-                       
-                       if (typeof data !== 'object') data = {};
-                       
-                       var request = _requests.get(callbackObject);
-                       if (request === undefined) {
-                               if (typeof callbackObject._ajaxSetup !== 'function') {
-                                       throw new TypeError("Callback object must implement at least _ajaxSetup().");
-                               }
-                               
-                               var options = callbackObject._ajaxSetup();
-                               
-                               options.pinData = true;
-                               options.callbackObject = callbackObject;
-                               
-                               if (!options.url) {
-                                       options.url = 'index.php?ajax-proxy/&t=' + SECURITY_TOKEN;
-                                       options.withCredentials = true;
-                               }
-                               
-                               request = new AjaxRequest(options);
-                               
-                               _requests.set(callbackObject, request);
-                       }
-                       
-                       var oldSuccess = null;
-                       var oldFailure = null;
-                       
-                       if (typeof success === 'function') {
-                               oldSuccess = request.getOption('success');
-                               request.setOption('success', success);
-                       }
-                       if (typeof failure === 'function') {
-                               oldFailure = request.getOption('failure');
-                               request.setOption('failure', failure);
-                       }
-                       
-                       request.setData(data);
-                       request.sendRequest();
-                       
-                       // restore callbacks
-                       if (oldSuccess !== null) request.setOption('success', oldSuccess);
-                       if (oldFailure !== null) request.setOption('failure', oldFailure);
-                       
-                       return request;
-               },
-               
-               /**
-                * Shorthand function to perform a single request against the WCF-API.
-                * 
-                * Please use `Ajax.api` if you're about to repeatedly send requests because this
-                * method will spawn an new and rather expensive `AjaxRequest` with each call.
-                *  
-                * @param       {object<string, *>}     options         request options
-                */
-               apiOnce: function(options) {
-                       // Fetch AjaxRequest, as it cannot be provided because of a circular dependency
-                       if (AjaxRequest === undefined) AjaxRequest = require('AjaxRequest');
-                       
-                       options.pinData = false;
-                       options.callbackObject = null;
-                       if (!options.url) {
-                               options.url = 'index.php?ajax-proxy/&t=' + SECURITY_TOKEN;
-                               options.withCredentials = true;
-                       }
-                       
-                       var request = new AjaxRequest(options);
-                       request.sendRequest(false);
-               },
-               
-               /**
-                * Returns the request object used for an earlier call to `api()`.
-                * 
-                * @param       {Object}        callbackObject  callback object
-                * @return      {AjaxRequest}
-                */
-               getRequestObject: function(callbackObject) {
-                       if (!_requests.has(callbackObject)) {
-                               throw new Error('Expected a previously used callback object, provided object is unknown.');
-                       }
-                       
-                       return _requests.get(callbackObject);
-               }
-       };
-});
-
-/**
- * Manages the invocation of the background queue.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/BackgroundQueue
- */
-define('WoltLabSuite/Core/BackgroundQueue',['Ajax'], function(Ajax) {
-       "use strict";
-       
-       var _invocations = 0;
-       var _isBusy = false;
-       var _url = '';
-       
-       /**
-        * @exports     WoltLabSuite/Core/BackgroundQueue
-        */
-       return {
-               /**
-                * Sets the url of the background queue perform action.
-                * 
-                * @param       {string}        url     background queue perform url
-                */
-               setUrl: function (url) {
-                       _url = url;
-               },
-               
-               /**
-                * Invokes the background queue.
-                */
-               invoke: function () {
-                       if (_url === '') {
-                               console.error('The background queue has not been initialized yet.');
-                               return;
-                       }
-                       
-                       if (_isBusy) return;
-                       
-                       _isBusy = true;
-                       
-                       Ajax.api(this);
-               },
-               
-               _ajaxSuccess: function (data) {
-                       _invocations++;
-                       
-                       // invoke the queue up to 5 times in a row
-                       if (data > 0 && _invocations < 5) {
-                               window.setTimeout(function () {
-                                       _isBusy = false;
-                                       this.invoke();
-                               }.bind(this), 1000);
-                       }
-                       else {
-                               _isBusy = false;
-                               _invocations = 0;
-                       }
-               },
-               
-               _ajaxSetup: function () {
-                       return {
-                               url: _url,
-                               ignoreError: true,
-                               silent: true
-                       }
-               }
-       }
-});
-
-/**
- * @license MIT or GPL-2.0
- * @fileOverview Favico animations
- * @author Miroslav Magda, http://blog.ejci.net
- * @source: https://github.com/ejci/favico.js
- * @version 0.3.10
- */
-
-/**
- * Create new favico instance
- * @param {Object} Options
- * @return {Object} Favico object
- * @example
- * var favico = new Favico({
- *    bgColor : '#d00',
- *    textColor : '#fff',
- *    fontFamily : 'sans-serif',
- *    fontStyle : 'bold',
- *    type : 'circle',
- *    position : 'down',
- *    animation : 'slide',
- *    elementId: false,
- *    element: null,
- *    dataUrl: function(url){},
- *    win: window
- * });
- */
-(function () {
-
-       var Favico = (function (opt) {
-               'use strict';
-               opt = (opt) ? opt : {};
-               var _def = {
-                       bgColor: '#d00',
-                       textColor: '#fff',
-                       fontFamily: 'sans-serif', //Arial,Verdana,Times New Roman,serif,sans-serif,...
-                       fontStyle: 'bold', //normal,italic,oblique,bold,bolder,lighter,100,200,300,400,500,600,700,800,900
-                       type: 'circle',
-                       position: 'down', // down, up, left, leftup (upleft)
-                       animation: 'slide',
-                       elementId: false,
-                       element: null,
-                       dataUrl: false,
-                       win: window
-               };
-               var _opt, _orig, _h, _w, _canvas, _context, _img, _ready, _lastBadge, _running, _readyCb, _stop, _browser, _animTimeout, _drawTimeout, _doc;
-
-               _browser = {};
-               _browser.ff = typeof InstallTrigger != 'undefined';
-               _browser.chrome = !!window.chrome;
-               _browser.opera = !!window.opera || navigator.userAgent.indexOf('Opera') >= 0;
-               _browser.ie = /*@cc_on!@*/false;
-               _browser.safari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0;
-               _browser.supported = (_browser.chrome || _browser.ff || _browser.opera);
-
-               var _queue = [];
-               _readyCb = function () {
-               };
-               _ready = _stop = false;
-               /**
-                * Initialize favico
-                */
-               var init = function () {
-                       //merge initial options
-                       _opt = merge(_def, opt);
-                       _opt.bgColor = hexToRgb(_opt.bgColor);
-                       _opt.textColor = hexToRgb(_opt.textColor);
-                       _opt.position = _opt.position.toLowerCase();
-                       _opt.animation = (animation.types['' + _opt.animation]) ? _opt.animation : _def.animation;
-
-                       _doc = _opt.win.document;
-
-                       var isUp = _opt.position.indexOf('up') > -1;
-                       var isLeft = _opt.position.indexOf('left') > -1;
-
-                       //transform the animations
-                       if (isUp || isLeft) {
-                               for (var a in animation.types) {
-                                       for (var i = 0; i < animation.types[a].length; i++) {
-                                               var step = animation.types[a][i];
-
-                                               if (isUp) {
-                                                       if (step.y < 0.6) {
-                                                               step.y = step.y - 0.4;
-                                                       } else {
-                                                               step.y = step.y - 2 * step.y + (1 - step.w);
-                                                       }
-                                               }
-
-                                               if (isLeft) {
-                                                       if (step.x < 0.6) {
-                                                               step.x = step.x - 0.4;
-                                                       } else {
-                                                               step.x = step.x - 2 * step.x + (1 - step.h);
-                                                       }
-                                               }
-
-                                               animation.types[a][i] = step;
-                                       }
-                               }
-                       }
-                       _opt.type = (type['' + _opt.type]) ? _opt.type : _def.type;
-
-                       _orig = link. getIcons();
-                       //create temp canvas
-                       _canvas = document.createElement('canvas');
-                       //create temp image
-                       _img = document.createElement('img');
-                       var lastIcon = _orig[_orig.length - 1];
-                       if (lastIcon.hasAttribute('href')) {
-                               _img.setAttribute('crossOrigin', 'anonymous');
-                               //get width/height
-                               _img.onload = function () {
-                                       _h = (_img.height > 0) ? _img.height : 32;
-                                       _w = (_img.width > 0) ? _img.width : 32;
-                                       _canvas.height = _h;
-                                       _canvas.width = _w;
-                                       _context = _canvas.getContext('2d');
-                                       icon.ready();
-                               };
-                               _img.setAttribute('src', lastIcon.getAttribute('href'));
-                       } else {
-                               _h = 32;
-                               _w = 32;
-                               _img.height = _h;
-                               _img.width = _w;
-                               _canvas.height = _h;
-                               _canvas.width = _w;
-                               _context = _canvas.getContext('2d');
-                               icon.ready();
-                       }
-
-               };
-               /**
-                * Icon namespace
-                */
-               var icon = {};
-               /**
-                * Icon is ready (reset icon) and start animation (if ther is any)
-                */
-               icon.ready = function () {
-                       _ready = true;
-                       icon.reset();
-                       _readyCb();
-               };
-               /**
-                * Reset icon to default state
-                */
-               icon.reset = function () {
-                       //reset
-                       if (!_ready) {
-                               return;
-                       }
-                       _queue = [];
-                       _lastBadge = false;
-                       _running = false;
-                       _context.clearRect(0, 0, _w, _h);
-                       _context.drawImage(_img, 0, 0, _w, _h);
-                       //_stop=true;
-                       link.setIcon(_canvas);
-                       //webcam('stop');
-                       //video('stop');
-                       window.clearTimeout(_animTimeout);
-                       window.clearTimeout(_drawTimeout);
-               };
-               /**
-                * Start animation
-                */
-               icon.start = function () {
-                       if (!_ready || _running) {
-                               return;
-                       }
-                       var finished = function () {
-                               _lastBadge = _queue[0];
-                               _running = false;
-                               if (_queue.length > 0) {
-                                       _queue.shift();
-                                       icon.start();
-                               } else {
-
-                               }
-                       };
-                       if (_queue.length > 0) {
-                               _running = true;
-                               var run = function () {
-                                       // apply options for this animation
-                                       ['type', 'animation', 'bgColor', 'textColor', 'fontFamily', 'fontStyle'].forEach(function (a) {
-                                               if (a in _queue[0].options) {
-                                                       _opt[a] = _queue[0].options[a];
-                                               }
-                                       });
-                                       animation.run(_queue[0].options, function () {
-                                               finished();
-                                       }, false);
-                               };
-                               if (_lastBadge) {
-                                       animation.run(_lastBadge.options, function () {
-                                               run();
-                                       }, true);
-                               } else {
-                                       run();
-                               }
-                       }
-               };
-
-               /**
-                * Badge types
-                */
-               var type = {};
-               var options = function (opt) {
-                       opt.n = ((typeof opt.n) === 'number') ? Math.abs(opt.n | 0) : opt.n;
-                       opt.x = _w * opt.x;
-                       opt.y = _h * opt.y;
-                       opt.w = _w * opt.w;
-                       opt.h = _h * opt.h;
-                       opt.len = ("" + opt.n).length;
-                       return opt;
-               };
-               /**
-                * Generate circle
-                * @param {Object} opt Badge options
-                */
-               type.circle = function (opt) {
-                       opt = options(opt);
-                       var more = false;
-                       if (opt.len === 2) {
-                               opt.x = opt.x - opt.w * 0.4;
-                               opt.w = opt.w * 1.4;
-                               more = true;
-                       } else if (opt.len >= 3) {
-                               opt.x = opt.x - opt.w * 0.65;
-                               opt.w = opt.w * 1.65;
-                               more = true;
-                       }
-                       _context.clearRect(0, 0, _w, _h);
-                       _context.drawImage(_img, 0, 0, _w, _h);
-                       _context.beginPath();
-                       _context.font = _opt.fontStyle + " " + Math.floor(opt.h * (opt.n > 99 ? 0.85 : 1)) + "px " + _opt.fontFamily;
-                       _context.textAlign = 'center';
-                       if (more) {
-                               _context.moveTo(opt.x + opt.w / 2, opt.y);
-                               _context.lineTo(opt.x + opt.w - opt.h / 2, opt.y);
-                               _context.quadraticCurveTo(opt.x + opt.w, opt.y, opt.x + opt.w, opt.y + opt.h / 2);
-                               _context.lineTo(opt.x + opt.w, opt.y + opt.h - opt.h / 2);
-                               _context.quadraticCurveTo(opt.x + opt.w, opt.y + opt.h, opt.x + opt.w - opt.h / 2, opt.y + opt.h);
-                               _context.lineTo(opt.x + opt.h / 2, opt.y + opt.h);
-                               _context.quadraticCurveTo(opt.x, opt.y + opt.h, opt.x, opt.y + opt.h - opt.h / 2);
-                               _context.lineTo(opt.x, opt.y + opt.h / 2);
-                               _context.quadraticCurveTo(opt.x, opt.y, opt.x + opt.h / 2, opt.y);
-                       } else {
-                               _context.arc(opt.x + opt.w / 2, opt.y + opt.h / 2, opt.h / 2, 0, 2 * Math.PI);
-                       }
-                       _context.fillStyle = 'rgba(' + _opt.bgColor.r + ',' + _opt.bgColor.g + ',' + _opt.bgColor.b + ',' + opt.o + ')';
-                       _context.fill();
-                       _context.closePath();
-                       _context.beginPath();
-                       _context.stroke();
-                       _context.fillStyle = 'rgba(' + _opt.textColor.r + ',' + _opt.textColor.g + ',' + _opt.textColor.b + ',' + opt.o + ')';
-                       //_context.fillText((more) ? '9+' : opt.n, Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.15));
-                       if ((typeof opt.n) === 'number' && opt.n > 999) {
-                               _context.fillText(((opt.n > 9999) ? 9 : Math.floor(opt.n / 1000)) + 'k+', Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.2));
-                       } else {
-                               _context.fillText(opt.n, Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.15));
-                       }
-                       _context.closePath();
-               };
-               /**
-                * Generate rectangle
-                * @param {Object} opt Badge options
-                */
-               type.rectangle = function (opt) {
-                       opt = options(opt);
-                       var more = false;
-                       if (opt.len === 2) {
-                               opt.x = opt.x - opt.w * 0.4;
-                               opt.w = opt.w * 1.4;
-                               more = true;
-                       } else if (opt.len >= 3) {
-                               opt.x = opt.x - opt.w * 0.65;
-                               opt.w = opt.w * 1.65;
-                               more = true;
-                       }
-                       _context.clearRect(0, 0, _w, _h);
-                       _context.drawImage(_img, 0, 0, _w, _h);
-                       _context.beginPath();
-                       _context.font = _opt.fontStyle + " " + Math.floor(opt.h * (opt.n > 99 ? 0.9 : 1)) + "px " + _opt.fontFamily;
-                       _context.textAlign = 'center';
-                       _context.fillStyle = 'rgba(' + _opt.bgColor.r + ',' + _opt.bgColor.g + ',' + _opt.bgColor.b + ',' + opt.o + ')';
-                       _context.fillRect(opt.x, opt.y, opt.w, opt.h);
-                       _context.fillStyle = 'rgba(' + _opt.textColor.r + ',' + _opt.textColor.g + ',' + _opt.textColor.b + ',' + opt.o + ')';
-                       //_context.fillText((more) ? '9+' : opt.n, Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.15));
-                       if ((typeof opt.n) === 'number' && opt.n > 999) {
-                               _context.fillText(((opt.n > 9999) ? 9 : Math.floor(opt.n / 1000)) + 'k+', Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.2));
-                       } else {
-                               _context.fillText(opt.n, Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.15));
-                       }
-                       _context.closePath();
-               };
-
-               /**
-                * Set badge
-                */
-               var badge = function (number, opts) {
-                       opts = ((typeof opts) === 'string' ? {
-                               animation: opts
-                       } : opts) || {};
-                       _readyCb = function () {
-                               try {
-                                       if (typeof (number) === 'number' ? (number > 0) : (number !== '')) {
-                                               var q = {
-                                                       type: 'badge',
-                                                       options: {
-                                                               n: number
-                                                       }
-                                               };
-                                               if ('animation' in opts && animation.types['' + opts.animation]) {
-                                                       q.options.animation = '' + opts.animation;
-                                               }
-                                               if ('type' in opts && type['' + opts.type]) {
-                                                       q.options.type = '' + opts.type;
-                                               }
-                                               ['bgColor', 'textColor'].forEach(function (o) {
-                                                       if (o in opts) {
-                                                               q.options[o] = hexToRgb(opts[o]);
-                                                       }
-                                               });
-                                               ['fontStyle', 'fontFamily'].forEach(function (o) {
-                                                       if (o in opts) {
-                                                               q.options[o] = opts[o];
-                                                       }
-                                               });
-                                               _queue.push(q);
-                                               if (_queue.length > 100) {
-                                                       throw new Error('Too many badges requests in queue.');
-                                               }
-                                               icon.start();
-                                       } else {
-                                               icon.reset();
-                                       }
-                               } catch (e) {
-                                       throw new Error('Error setting badge. Message: ' + e.message);
-                               }
-                       };
-                       if (_ready) {
-                               _readyCb();
-                       }
-               };
-
-               /**
-                * Set image as icon
-                */
-               var image = function (imageElement) {
-                       _readyCb = function () {
-                               try {
-                                       var w = imageElement.width;
-                                       var h = imageElement.height;
-                                       var newImg = document.createElement('img');
-                                       var ratio = (w / _w < h / _h) ? (w / _w) : (h / _h);
-                                       newImg.setAttribute('crossOrigin', 'anonymous');
-                                       newImg.onload=function(){
-                                               _context.clearRect(0, 0, _w, _h);
-                                               _context.drawImage(newImg, 0, 0, _w, _h);
-                                               link.setIcon(_canvas);
-                                       };
-                                       newImg.setAttribute('src', imageElement.getAttribute('src'));
-                                       newImg.height = (h / ratio);
-                                       newImg.width = (w / ratio);
-                               } catch (e) {
-                                       throw new Error('Error setting image. Message: ' + e.message);
-                               }
-                       };
-                       if (_ready) {
-                               _readyCb();
-                       }
-               };
-               /**
-                * Set the icon from a source url. Won't work with badges.
-                */
-               var rawImageSrc = function (url) {
-                       _readyCb = function() {
-                               link.setIconSrc(url);
-                       };
-                       if (_ready) {
-                               _readyCb();
-                       }
-               };
-               /**
-                * Set video as icon
-                */
-               var video = function (videoElement) {
-                       _readyCb = function () {
-                               try {
-                                       if (videoElement === 'stop') {
-                                               _stop = true;
-                                               icon.reset();
-                                               _stop = false;
-                                               return;
-                                       }
-                                       //var w = videoElement.width;
-                                       //var h = videoElement.height;
-                                       //var ratio = (w / _w < h / _h) ? (w / _w) : (h / _h);
-                                       videoElement.addEventListener('play', function () {
-                                               drawVideo(this);
-                                       }, false);
-
-                               } catch (e) {
-                                       throw new Error('Error setting video. Message: ' + e.message);
-                               }
-                       };
-                       if (_ready) {
-                               _readyCb();
-                       }
-               };
-               /**
-                * Set video as icon
-                */
-               var webcam = function (action) {
-                       //UR
-                       if (!window.URL || !window.URL.createObjectURL) {
-                               window.URL = window.URL || {};
-                               window.URL.createObjectURL = function (obj) {
-                                       return obj;
-                               };
-                       }
-                       if (_browser.supported) {
-                               var newVideo = false;
-                               navigator.getUserMedia = navigator.getUserMedia || navigator.oGetUserMedia || navigator.msGetUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia;
-                               _readyCb = function () {
-                                       try {
-                                               if (action === 'stop') {
-                                                       _stop = true;
-                                                       icon.reset();
-                                                       _stop = false;
-                                                       return;
-                                               }
-                                               newVideo = document.createElement('video');
-                                               newVideo.width = _w;
-                                               newVideo.height = _h;
-                                               navigator.getUserMedia({
-                                                       video: true,
-                                                       audio: false
-                                               }, function (stream) {
-                                                       newVideo.src = URL.createObjectURL(stream);
-                                                       newVideo.play();
-                                                       drawVideo(newVideo);
-                                               }, function () {
-                                               });
-                                       } catch (e) {
-                                               throw new Error('Error setting webcam. Message: ' + e.message);
-                                       }
-                               };
-                               if (_ready) {
-                                       _readyCb();
-                               }
-                       }
-
-               };
-
-               var setOpt = function (key, value) {
-                       var opts = key;
-                       if (!(value == null && Object.prototype.toString.call(key) == '[object Object]')) {
-                               opts = {};
-                               opts[key] = value;
-                       }
-
-                       var keys = Object.keys(opts);
-                       for (var i = 0; i < keys.length; i++) {
-                               if (keys[i] == 'bgColor' || keys[i] == 'textColor') {
-                                       _opt[keys[i]] = hexToRgb(opts[keys[i]]);
-                               } else {
-                                       _opt[keys[i]] = opts[keys[i]];
-                               }
-                       }
-
-                       _queue.push(_lastBadge);
-                       icon.start();
-               };
-
-               /**
-                * Draw video to context and repeat :)
-                */
-               function drawVideo(video) {
-                       if (video.paused || video.ended || _stop) {
-                               return false;
-                       }
-                       //nasty hack for FF webcam (Thanks to Julian Ä†wirko, kontakt@redsunmedia.pl)
-                       try {
-                               _context.clearRect(0, 0, _w, _h);
-                               _context.drawImage(video, 0, 0, _w, _h);
-                       } catch (e) {
-
-                       }
-                       _drawTimeout = setTimeout(function () {
-                               drawVideo(video);
-                       }, animation.duration);
-                       link.setIcon(_canvas);
-               }
-
-               var link = {};
-               /**
-                * Get icons from HEAD tag or create a new <link> element
-                */
-               link.getIcons = function () {
-                       var elms = [];
-                       //get link element
-                       var getLinks = function () {
-                               var icons = [];
-                               var links = _doc.getElementsByTagName('head')[0].getElementsByTagName('link');
-                               for (var i = 0; i < links.length; i++) {
-                                       if ((/(^|\s)icon(\s|$)/i).test(links[i].getAttribute('rel'))) {
-                                               icons.push(links[i]);
-                                       }
-                               }
-                               return icons;
-                       };
-                       if (_opt.element) {
-                               elms = [_opt.element];
-                       } else if (_opt.elementId) {
-                               //if img element identified by elementId
-                               elms = [_doc.getElementById(_opt.elementId)];
-                               elms[0].setAttribute('href', elms[0].getAttribute('src'));
-                       } else {
-                               //if link element
-                               elms = getLinks();
-                               if (elms.length === 0) {
-                                       elms = [_doc.createElement('link')];
-                                       elms[0].setAttribute('rel', 'icon');
-                                       _doc.getElementsByTagName('head')[0].appendChild(elms[0]);
-                               }
-                       }
-                       elms.forEach(function(item) {
-                               item.setAttribute('type', 'image/png');
-                       });
-                       return elms;
-               };
-               link.setIcon = function (canvas) {
-                       var url = canvas.toDataURL('image/png');
-                       link.setIconSrc(url);
-               };
-               link.setIconSrc = function (url) {
-                       if (_opt.dataUrl) {
-                               //if using custom exporter
-                               _opt.dataUrl(url);
-                       }
-                       if (_opt.element) {
-                               _opt.element.setAttribute('href', url);
-                               _opt.element.setAttribute('src', url);
-                       } else if (_opt.elementId) {
-                               //if is attached to element (image)
-                               var elm = _doc.getElementById(_opt.elementId);
-                               elm.setAttribute('href', url);
-                               elm.setAttribute('src', url);
-                       } else {
-                               //if is attached to fav icon
-                               if (_browser.ff || _browser.opera) {
-                                       //for FF we need to "recreate" element, atach to dom and remove old <link>
-                                       //var originalType = _orig.getAttribute('rel');
-                                       var old = _orig[_orig.length - 1];
-                                       var newIcon = _doc.createElement('link');
-                                       _orig = [newIcon];
-                                       //_orig.setAttribute('rel', originalType);
-                                       if (_browser.opera) {
-                                               newIcon.setAttribute('rel', 'icon');
-                                       }
-                                       newIcon.setAttribute('rel', 'icon');
-                                       newIcon.setAttribute('type', 'image/png');
-                                       _doc.getElementsByTagName('head')[0].appendChild(newIcon);
-                                       newIcon.setAttribute('href', url);
-                                       if (old.parentNode) {
-                                               old.parentNode.removeChild(old);
-                                       }
-                               } else {
-                                       _orig.forEach(function(icon) {
-                                               icon.setAttribute('href', url);
-                                       });
-                               }
-                       }
-               };
-
-               //http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb#answer-5624139
-               //HEX to RGB convertor
-               function hexToRgb(hex) {
-                       var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
-                       hex = hex.replace(shorthandRegex, function (m, r, g, b) {
-                               return r + r + g + g + b + b;
-                       });
-                       var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
-                       return result ? {
-                               r: parseInt(result[1], 16),
-                               g: parseInt(result[2], 16),
-                               b: parseInt(result[3], 16)
-                       } : false;
-               }
-
-               /**
-                * Merge options
-                */
-               function merge(def, opt) {
-                       var mergedOpt = {};
-                       var attrname;
-                       for (attrname in def) {
-                               mergedOpt[attrname] = def[attrname];
-                       }
-                       for (attrname in opt) {
-                               mergedOpt[attrname] = opt[attrname];
-                       }
-                       return mergedOpt;
-               }
-
-               /**
-                * Cross-browser page visibility shim
-                * http://stackoverflow.com/questions/12536562/detect-whether-a-window-is-visible
-                */
-               function isPageHidden() {
-                       return _doc.hidden || _doc.msHidden || _doc.webkitHidden || _doc.mozHidden;
-               }
-
-               /**
-                * @namespace animation
-                */
-               var animation = {};
-               /**
-                * Animation "frame" duration
-                */
-               animation.duration = 40;
-               /**
-                * Animation types (none,fade,pop,slide)
-                */
-               animation.types = {};
-               animation.types.fade = [{
-                       x: 0.4,
-                       y: 0.4,
-                       w: 0.6,
-                       h: 0.6,
-                       o: 0.0
-               }, {
-                               x: 0.4,
-                               y: 0.4,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 0.1
-                       }, {
-                               x: 0.4,
-                               y: 0.4,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 0.2
-                       }, {
-                               x: 0.4,
-                               y: 0.4,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 0.3
-                       }, {
-                               x: 0.4,
-                               y: 0.4,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 0.4
-                       }, {
-                               x: 0.4,
-                               y: 0.4,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 0.5
-                       }, {
-                               x: 0.4,
-                               y: 0.4,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 0.6
-                       }, {
-                               x: 0.4,
-                               y: 0.4,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 0.7
-                       }, {
-                               x: 0.4,
-                               y: 0.4,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 0.8
-                       }, {
-                               x: 0.4,
-                               y: 0.4,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 0.9
-                       }, {
-                               x: 0.4,
-                               y: 0.4,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 1.0
-                       }];
-               animation.types.none = [{
-                       x: 0.4,
-                       y: 0.4,
-                       w: 0.6,
-                       h: 0.6,
-                       o: 1
-               }];
-               animation.types.pop = [{
-                       x: 1,
-                       y: 1,
-                       w: 0,
-                       h: 0,
-                       o: 1
-               }, {
-                               x: 0.9,
-                               y: 0.9,
-                               w: 0.1,
-                               h: 0.1,
-                               o: 1
-                       }, {
-                               x: 0.8,
-                               y: 0.8,
-                               w: 0.2,
-                               h: 0.2,
-                               o: 1
-                       }, {
-                               x: 0.7,
-                               y: 0.7,
-                               w: 0.3,
-                               h: 0.3,
-                               o: 1
-                       }, {
-                               x: 0.6,
-                               y: 0.6,
-                               w: 0.4,
-                               h: 0.4,
-                               o: 1
-                       }, {
-                               x: 0.5,
-                               y: 0.5,
-                               w: 0.5,
-                               h: 0.5,
-                               o: 1
-                       }, {
-                               x: 0.4,
-                               y: 0.4,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 1
-                       }];
-               animation.types.popFade = [{
-                       x: 0.75,
-                       y: 0.75,
-                       w: 0,
-                       h: 0,
-                       o: 0
-               }, {
-                               x: 0.65,
-                               y: 0.65,
-                               w: 0.1,
-                               h: 0.1,
-                               o: 0.2
-                       }, {
-                               x: 0.6,
-                               y: 0.6,
-                               w: 0.2,
-                               h: 0.2,
-                               o: 0.4
-                       }, {
-                               x: 0.55,
-                               y: 0.55,
-                               w: 0.3,
-                               h: 0.3,
-                               o: 0.6
-                       }, {
-                               x: 0.50,
-                               y: 0.50,
-                               w: 0.4,
-                               h: 0.4,
-                               o: 0.8
-                       }, {
-                               x: 0.45,
-                               y: 0.45,
-                               w: 0.5,
-                               h: 0.5,
-                               o: 0.9
-                       }, {
-                               x: 0.4,
-                               y: 0.4,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 1
-                       }];
-               animation.types.slide = [{
-                       x: 0.4,
-                       y: 1,
-                       w: 0.6,
-                       h: 0.6,
-                       o: 1
-               }, {
-                               x: 0.4,
-                               y: 0.9,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 1
-                       }, {
-                               x: 0.4,
-                               y: 0.9,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 1
-                       }, {
-                               x: 0.4,
-                               y: 0.8,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 1
-                       }, {
-                               x: 0.4,
-                               y: 0.7,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 1
-                       }, {
-                               x: 0.4,
-                               y: 0.6,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 1
-                       }, {
-                               x: 0.4,
-                               y: 0.5,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 1
-                       }, {
-                               x: 0.4,
-                               y: 0.4,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 1
-                       }];
-               /**
-                * Run animation
-                * @param {Object} opt Animation options
-                * @param {Object} cb Callabak after all steps are done
-                * @param {Object} revert Reverse order? true|false
-                * @param {Object} step Optional step number (frame bumber)
-                */
-               animation.run = function (opt, cb, revert, step) {
-                       var animationType = animation.types[isPageHidden() ? 'none' : _opt.animation];
-                       if (revert === true) {
-                               step = (typeof step !== 'undefined') ? step : animationType.length - 1;
-                       } else {
-                               step = (typeof step !== 'undefined') ? step : 0;
-                       }
-                       cb = (cb) ? cb : function () {
-                       };
-                       if ((step < animationType.length) && (step >= 0)) {
-                               type[_opt.type](merge(opt, animationType[step]));
-                               _animTimeout = setTimeout(function () {
-                                       if (revert) {
-                                               step = step - 1;
-                                       } else {
-                                               step = step + 1;
-                                       }
-                                       animation.run(opt, cb, revert, step);
-                               }, animation.duration);
-
-                               link.setIcon(_canvas);
-                       } else {
-                               cb();
-                               return;
-                       }
-               };
-               //auto init
-               init();
-               return {
-                       badge: badge,
-                       video: video,
-                       image: image,
-                       rawImageSrc: rawImageSrc,
-                       webcam: webcam,
-                       setOpt: setOpt,
-                       reset: icon.reset,
-                       browser: {
-                               supported: _browser.supported
-                       }
-               };
-       });
-
-       // AMD / RequireJS
-       if (typeof define !== 'undefined' && define.amd) {
-               define('favico',[], function () {
-                       return Favico;
-               });
-       }
-       // CommonJS
-       else if (typeof module !== 'undefined' && module.exports) {
-               module.exports = Favico;
-       }
-       // included directly via <script> tag
-       else {
-               this.Favico = Favico;
-       }
-
-})();
-
-/*!
- * enquire.js v2.1.2 - Awesome Media Queries in JavaScript
- * Copyright (c) 2014 Nick Williams - http://wicky.nillia.ms/enquire.js
- * License: MIT (http://www.opensource.org/licenses/mit-license.php)
- */
-
-;(function (name, context, factory) {
-       var matchMedia = window.matchMedia;
-
-       if (typeof module !== 'undefined' && module.exports) {
-               module.exports = factory(matchMedia);
-       }
-       else if (typeof define === 'function' && define.amd) {
-               define('enquire',[],function() {
-                       return (context[name] = factory(matchMedia));
-               });
-       }
-       else {
-               context[name] = factory(matchMedia);
-       }
-}('enquire', this, function (matchMedia) {
-
-       'use strict';
-
-    /*jshint unused:false */
-    /**
-     * Helper function for iterating over a collection
-     *
-     * @param collection
-     * @param fn
-     */
-    function each(collection, fn) {
-        var i      = 0,
-            length = collection.length,
-            cont;
-
-        for(i; i < length; i++) {
-            cont = fn(collection[i], i);
-            if(cont === false) {
-                break; //allow early exit
-            }
-        }
-    }
-
-    /**
-     * Helper function for determining whether target object is an array
-     *
-     * @param target the object under test
-     * @return {Boolean} true if array, false otherwise
-     */
-    function isArray(target) {
-        return Object.prototype.toString.apply(target) === '[object Array]';
-    }
-
-    /**
-     * Helper function for determining whether target object is a function
-     *
-     * @param target the object under test
-     * @return {Boolean} true if function, false otherwise
-     */
-    function isFunction(target) {
-        return typeof target === 'function';
-    }
-
-    /**
-     * Delegate to handle a media query being matched and unmatched.
-     *
-     * @param {object} options
-     * @param {function} options.match callback for when the media query is matched
-     * @param {function} [options.unmatch] callback for when the media query is unmatched
-     * @param {function} [options.setup] one-time callback triggered the first time a query is matched
-     * @param {boolean} [options.deferSetup=false] should the setup callback be run immediately, rather than first time query is matched?
-     * @constructor
-     */
-    function QueryHandler(options) {
-        this.options = options;
-        !options.deferSetup && this.setup();
-    }
-    QueryHandler.prototype = {
-
-        /**
-         * coordinates setup of the handler
-         *
-         * @function
-         */
-        setup : function() {
-            if(this.options.setup) {
-                this.options.setup();
-            }
-            this.initialised = true;
-        },
-
-        /**
-         * coordinates setup and triggering of the handler
-         *
-         * @function
-         */
-        on : function() {
-            !this.initialised && this.setup();
-            this.options.match && this.options.match();
-        },
-
-        /**
-         * coordinates the unmatch event for the handler
-         *
-         * @function
-         */
-        off : function() {
-            this.options.unmatch && this.options.unmatch();
-        },
-
-        /**
-         * called when a handler is to be destroyed.
-         * delegates to the destroy or unmatch callbacks, depending on availability.
-         *
-         * @function
-         */
-        destroy : function() {
-            this.options.destroy ? this.options.destroy() : this.off();
-        },
-
-        /**
-         * determines equality by reference.
-         * if object is supplied compare options, if function, compare match callback
-         *
-         * @function
-         * @param {object || function} [target] the target for comparison
-         */
-        equals : function(target) {
-            return this.options === target || this.options.match === target;
-        }
-
-    };
-    /**
-     * Represents a single media query, manages it's state and registered handlers for this query
-     *
-     * @constructor
-     * @param {string} query the media query string
-     * @param {boolean} [isUnconditional=false] whether the media query should run regardless of whether the conditions are met. Primarily for helping older browsers deal with mobile-first design
-     */
-    function MediaQuery(query, isUnconditional) {
-        this.query = query;
-        this.isUnconditional = isUnconditional;
-        this.handlers = [];
-        this.mql = matchMedia(query);
-
-        var self = this;
-        this.listener = function(mql) {
-            self.mql = mql;
-            self.assess();
-        };
-        this.mql.addListener(this.listener);
-    }
-    MediaQuery.prototype = {
-
-        /**
-         * add a handler for this query, triggering if already active
-         *
-         * @param {object} handler
-         * @param {function} handler.match callback for when query is activated
-         * @param {function} [handler.unmatch] callback for when query is deactivated
-         * @param {function} [handler.setup] callback for immediate execution when a query handler is registered
-         * @param {boolean} [handler.deferSetup=false] should the setup callback be deferred until the first time the handler is matched?
-         */
-        addHandler : function(handler) {
-            var qh = new QueryHandler(handler);
-            this.handlers.push(qh);
-
-            this.matches() && qh.on();
-        },
-
-        /**
-         * removes the given handler from the collection, and calls it's destroy methods
-         * 
-         * @param {object || function} handler the handler to remove
-         */
-        removeHandler : function(handler) {
-            var handlers = this.handlers;
-            each(handlers, function(h, i) {
-                if(h.equals(handler)) {
-                    h.destroy();
-                    return !handlers.splice(i,1); //remove from array and exit each early
-                }
-            });
-        },
-
-        /**
-         * Determine whether the media query should be considered a match
-         * 
-         * @return {Boolean} true if media query can be considered a match, false otherwise
-         */
-        matches : function() {
-            return this.mql.matches || this.isUnconditional;
-        },
-
-        /**
-         * Clears all handlers and unbinds events
-         */
-        clear : function() {
-            each(this.handlers, function(handler) {
-                handler.destroy();
-            });
-            this.mql.removeListener(this.listener);
-            this.handlers.length = 0; //clear array
-        },
-
-        /*
-         * Assesses the query, turning on all handlers if it matches, turning them off if it doesn't match
-         */
-        assess : function() {
-            var action = this.matches() ? 'on' : 'off';
-
-            each(this.handlers, function(handler) {
-                handler[action]();
-            });
-        }
-    };
-    /**
-     * Allows for registration of query handlers.
-     * Manages the query handler's state and is responsible for wiring up browser events
-     *
-     * @constructor
-     */
-    function MediaQueryDispatch () {
-        if(!matchMedia) {
-            throw new Error('matchMedia not present, legacy browsers require a polyfill');
-        }
-
-        this.queries = {};
-        this.browserIsIncapable = !matchMedia('only all').matches;
-    }
-
-    MediaQueryDispatch.prototype = {
-
-        /**
-         * Registers a handler for the given media query
-         *
-         * @param {string} q the media query
-         * @param {object || Array || Function} options either a single query handler object, a function, or an array of query handlers
-         * @param {function} options.match fired when query matched
-         * @param {function} [options.unmatch] fired when a query is no longer matched
-         * @param {function} [options.setup] fired when handler first triggered
-         * @param {boolean} [options.deferSetup=false] whether setup should be run immediately or deferred until query is first matched
-         * @param {boolean} [shouldDegrade=false] whether this particular media query should always run on incapable browsers
-         */
-        register : function(q, options, shouldDegrade) {
-            var queries         = this.queries,
-                isUnconditional = shouldDegrade && this.browserIsIncapable;
-
-            if(!queries[q]) {
-                queries[q] = new MediaQuery(q, isUnconditional);
-            }
-
-            //normalise to object in an array
-            if(isFunction(options)) {
-                options = { match : options };
-            }
-            if(!isArray(options)) {
-                options = [options];
-            }
-            each(options, function(handler) {
-                if (isFunction(handler)) {
-                    handler = { match : handler };
-                }
-                queries[q].addHandler(handler);
-            });
-
-            return this;
-        },
-
-        /**
-         * unregisters a query and all it's handlers, or a specific handler for a query
-         *
-         * @param {string} q the media query to target
-         * @param {object || function} [handler] specific handler to unregister
-         */
-        unregister : function(q, handler) {
-            var query = this.queries[q];
-
-            if(query) {
-                if(handler) {
-                    query.removeHandler(handler);
-                }
-                else {
-                    query.clear();
-                    delete this.queries[q];
-                }
-            }
-
-            return this;
-        }
-    };
-
-       return new MediaQueryDispatch();
-
-}));
-/* perfect-scrollbar v0.6.16 */
-(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
-'use strict';
-
-var ps = require('../main');
-
-if (typeof define === 'function' && define.amd) {
-  // AMD
-  define('perfect-scrollbar',ps);
-} else {
-  // Add to a global object.
-  window.PerfectScrollbar = ps;
-  if (typeof window.Ps === 'undefined') {
-    window.Ps = ps;
-  }
-}
-
-},{"../main":7}],2:[function(require,module,exports){
-'use strict';
-
-function oldAdd(element, className) {
-  var classes = element.className.split(' ');
-  if (classes.indexOf(className) < 0) {
-    classes.push(className);
-  }
-  element.className = classes.join(' ');
-}
-
-function oldRemove(element, className) {
-  var classes = element.className.split(' ');
-  var idx = classes.indexOf(className);
-  if (idx >= 0) {
-    classes.splice(idx, 1);
-  }
-  element.className = classes.join(' ');
-}
-
-exports.add = function (element, className) {
-  if (element.classList) {
-    element.classList.add(className);
-  } else {
-    oldAdd(element, className);
-  }
-};
-
-exports.remove = function (element, className) {
-  if (element.classList) {
-    element.classList.remove(className);
-  } else {
-    oldRemove(element, className);
-  }
-};
-
-exports.list = function (element) {
-  if (element.classList) {
-    return Array.prototype.slice.apply(element.classList);
-  } else {
-    return element.className.split(' ');
-  }
-};
-
-},{}],3:[function(require,module,exports){
-'use strict';
-
-var DOM = {};
-
-DOM.e = function (tagName, className) {
-  var element = document.createElement(tagName);
-  element.className = className;
-  return element;
-};
-
-DOM.appendTo = function (child, parent) {
-  parent.appendChild(child);
-  return child;
-};
-
-function cssGet(element, styleName) {
-  return window.getComputedStyle(element)[styleName];
-}
-
-function cssSet(element, styleName, styleValue) {
-  if (typeof styleValue === 'number') {
-    styleValue = styleValue.toString() + 'px';
-  }
-  element.style[styleName] = styleValue;
-  return element;
-}
-
-function cssMultiSet(element, obj) {
-  for (var key in obj) {
-    var val = obj[key];
-    if (typeof val === 'number') {
-      val = val.toString() + 'px';
-    }
-    element.style[key] = val;
-  }
-  return element;
-}
-
-DOM.css = function (element, styleNameOrObject, styleValue) {
-  if (typeof styleNameOrObject === 'object') {
-    // multiple set with object
-    return cssMultiSet(element, styleNameOrObject);
-  } else {
-    if (typeof styleValue === 'undefined') {
-      return cssGet(element, styleNameOrObject);
-    } else {
-      return cssSet(element, styleNameOrObject, styleValue);
-    }
-  }
-};
-
-DOM.matches = function (element, query) {
-  if (typeof element.matches !== 'undefined') {
-    return element.matches(query);
-  } else {
-    if (typeof element.matchesSelector !== 'undefined') {
-      return element.matchesSelector(query);
-    } else if (typeof element.webkitMatchesSelector !== 'undefined') {
-      return element.webkitMatchesSelector(query);
-    } else if (typeof element.mozMatchesSelector !== 'undefined') {
-      return element.mozMatchesSelector(query);
-    } else if (typeof element.msMatchesSelector !== 'undefined') {
-      return element.msMatchesSelector(query);
-    }
-  }
-};
-
-DOM.remove = function (element) {
-  if (typeof element.remove !== 'undefined') {
-    element.remove();
-  } else {
-    if (element.parentNode) {
-      element.parentNode.removeChild(element);
-    }
-  }
-};
-
-DOM.queryChildren = function (element, selector) {
-  return Array.prototype.filter.call(element.childNodes, function (child) {
-    return DOM.matches(child, selector);
-  });
-};
-
-module.exports = DOM;
-
-},{}],4:[function(require,module,exports){
-'use strict';
-
-var EventElement = function (element) {
-  this.element = element;
-  this.events = {};
-};
-
-EventElement.prototype.bind = function (eventName, handler) {
-  if (typeof this.events[eventName] === 'undefined') {
-    this.events[eventName] = [];
-  }
-  this.events[eventName].push(handler);
-  this.element.addEventListener(eventName, handler, false);
-};
-
-EventElement.prototype.unbind = function (eventName, handler) {
-  var isHandlerProvided = (typeof handler !== 'undefined');
-  this.events[eventName] = this.events[eventName].filter(function (hdlr) {
-    if (isHandlerProvided && hdlr !== handler) {
-      return true;
-    }
-    this.element.removeEventListener(eventName, hdlr, false);
-    return false;
-  }, this);
-};
-
-EventElement.prototype.unbindAll = function () {
-  for (var name in this.events) {
-    this.unbind(name);
-  }
-};
-
-var EventManager = function () {
-  this.eventElements = [];
-};
-
-EventManager.prototype.eventElement = function (element) {
-  var ee = this.eventElements.filter(function (eventElement) {
-    return eventElement.element === element;
-  })[0];
-  if (typeof ee === 'undefined') {
-    ee = new EventElement(element);
-    this.eventElements.push(ee);
-  }
-  return ee;
-};
-
-EventManager.prototype.bind = function (element, eventName, handler) {
-  this.eventElement(element).bind(eventName, handler);
-};
-
-EventManager.prototype.unbind = function (element, eventName, handler) {
-  this.eventElement(element).unbind(eventName, handler);
-};
-
-EventManager.prototype.unbindAll = function () {
-  for (var i = 0; i < this.eventElements.length; i++) {
-    this.eventElements[i].unbindAll();
-  }
-};
-
-EventManager.prototype.once = function (element, eventName, handler) {
-  var ee = this.eventElement(element);
-  var onceHandler = function (e) {
-    ee.unbind(eventName, onceHandler);
-    handler(e);
-  };
-  ee.bind(eventName, onceHandler);
-};
-
-module.exports = EventManager;
-
-},{}],5:[function(require,module,exports){
-'use strict';
-
-module.exports = (function () {
-  function s4() {
-    return Math.floor((1 + Math.random()) * 0x10000)
-               .toString(16)
-               .substring(1);
-  }
-  return function () {
-    return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
-           s4() + '-' + s4() + s4() + s4();
-  };
-})();
-
-},{}],6:[function(require,module,exports){
-'use strict';
-
-var cls = require('./class');
-var dom = require('./dom');
-
-var toInt = exports.toInt = function (x) {
-  return parseInt(x, 10) || 0;
-};
-
-var clone = exports.clone = function (obj) {
-  if (!obj) {
-    return null;
-  } else if (obj.constructor === Array) {
-    return obj.map(clone);
-  } else if (typeof obj === 'object') {
-    var result = {};
-    for (var key in obj) {
-      result[key] = clone(obj[key]);
-    }
-    return result;
-  } else {
-    return obj;
-  }
-};
-
-exports.extend = function (original, source) {
-  var result = clone(original);
-  for (var key in source) {
-    result[key] = clone(source[key]);
-  }
-  return result;
-};
-
-exports.isEditable = function (el) {
-  return dom.matches(el, "input,[contenteditable]") ||
-         dom.matches(el, "select,[contenteditable]") ||
-         dom.matches(el, "textarea,[contenteditable]") ||
-         dom.matches(el, "button,[contenteditable]");
-};
-
-exports.removePsClasses = function (element) {
-  var clsList = cls.list(element);
-  for (var i = 0; i < clsList.length; i++) {
-    var className = clsList[i];
-    if (className.indexOf('ps-') === 0) {
-      cls.remove(element, className);
-    }
-  }
-};
-
-exports.outerWidth = function (element) {
-  return toInt(dom.css(element, 'width')) +
-         toInt(dom.css(element, 'paddingLeft')) +
-         toInt(dom.css(element, 'paddingRight')) +
-         toInt(dom.css(element, 'borderLeftWidth')) +
-         toInt(dom.css(element, 'borderRightWidth'));
-};
-
-exports.startScrolling = function (element, axis) {
-  cls.add(element, 'ps-in-scrolling');
-  if (typeof axis !== 'undefined') {
-    cls.add(element, 'ps-' + axis);
-  } else {
-    cls.add(element, 'ps-x');
-    cls.add(element, 'ps-y');
-  }
-};
-
-exports.stopScrolling = function (element, axis) {
-  cls.remove(element, 'ps-in-scrolling');
-  if (typeof axis !== 'undefined') {
-    cls.remove(element, 'ps-' + axis);
-  } else {
-    cls.remove(element, 'ps-x');
-    cls.remove(element, 'ps-y');
-  }
-};
-
-exports.env = {
-  isWebKit: 'WebkitAppearance' in document.documentElement.style,
-  supportsTouch: (('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch),
-  supportsIePointer: window.navigator.msMaxTouchPoints !== null
-};
-
-},{"./class":2,"./dom":3}],7:[function(require,module,exports){
-'use strict';
-
-var destroy = require('./plugin/destroy');
-var initialize = require('./plugin/initialize');
-var update = require('./plugin/update');
-
-module.exports = {
-  initialize: initialize,
-  update: update,
-  destroy: destroy
-};
-
-},{"./plugin/destroy":9,"./plugin/initialize":17,"./plugin/update":21}],8:[function(require,module,exports){
-'use strict';
-
-module.exports = {
-  handlers: ['click-rail', 'drag-scrollbar', 'keyboard', 'wheel', 'touch'],
-  maxScrollbarLength: null,
-  minScrollbarLength: null,
-  scrollXMarginOffset: 0,
-  scrollYMarginOffset: 0,
-  suppressScrollX: false,
-  suppressScrollY: false,
-  swipePropagation: true,
-  useBothWheelAxes: false,
-  wheelPropagation: false,
-  wheelSpeed: 1,
-  theme: 'default'
-};
-
-},{}],9:[function(require,module,exports){
-'use strict';
-
-var _ = require('../lib/helper');
-var dom = require('../lib/dom');
-var instances = require('./instances');
-
-module.exports = function (element) {
-  var i = instances.get(element);
-
-  if (!i) {
-    return;
-  }
-
-  i.event.unbindAll();
-  dom.remove(i.scrollbarX);
-  dom.remove(i.scrollbarY);
-  dom.remove(i.scrollbarXRail);
-  dom.remove(i.scrollbarYRail);
-  _.removePsClasses(element);
-
-  instances.remove(element);
-};
-
-},{"../lib/dom":3,"../lib/helper":6,"./instances":18}],10:[function(require,module,exports){
-'use strict';
-
-var instances = require('../instances');
-var updateGeometry = require('../update-geometry');
-var updateScroll = require('../update-scroll');
-
-function bindClickRailHandler(element, i) {
-  function pageOffset(el) {
-    return el.getBoundingClientRect();
-  }
-  var stopPropagation = function (e) { e.stopPropagation(); };
-
-  i.event.bind(i.scrollbarY, 'click', stopPropagation);
-  i.event.bind(i.scrollbarYRail, 'click', function (e) {
-    var positionTop = e.pageY - window.pageYOffset - pageOffset(i.scrollbarYRail).top;
-    var direction = positionTop > i.scrollbarYTop ? 1 : -1;
-
-    updateScroll(element, 'top', element.scrollTop + direction * i.containerHeight);
-    updateGeometry(element);
-
-    e.stopPropagation();
-  });
-
-  i.event.bind(i.scrollbarX, 'click', stopPropagation);
-  i.event.bind(i.scrollbarXRail, 'click', function (e) {
-    var positionLeft = e.pageX - window.pageXOffset - pageOffset(i.scrollbarXRail).left;
-    var direction = positionLeft > i.scrollbarXLeft ? 1 : -1;
-
-    updateScroll(element, 'left', element.scrollLeft + direction * i.containerWidth);
-    updateGeometry(element);
-
-    e.stopPropagation();
-  });
-}
-
-module.exports = function (element) {
-  var i = instances.get(element);
-  bindClickRailHandler(element, i);
-};
-
-},{"../instances":18,"../update-geometry":19,"../update-scroll":20}],11:[function(require,module,exports){
-'use strict';
-
-var _ = require('../../lib/helper');
-var dom = require('../../lib/dom');
-var instances = require('../instances');
-var updateGeometry = require('../update-geometry');
-var updateScroll = require('../update-scroll');
-
-function bindMouseScrollXHandler(element, i) {
-  var currentLeft = null;
-  var currentPageX = null;
-
-  function updateScrollLeft(deltaX) {
-    var newLeft = currentLeft + (deltaX * i.railXRatio);
-    var maxLeft = Math.max(0, i.scrollbarXRail.getBoundingClientRect().left) + (i.railXRatio * (i.railXWidth - i.scrollbarXWidth));
-
-    if (newLeft < 0) {
-      i.scrollbarXLeft = 0;
-    } else if (newLeft > maxLeft) {
-      i.scrollbarXLeft = maxLeft;
-    } else {
-      i.scrollbarXLeft = newLeft;
-    }
-
-    var scrollLeft = _.toInt(i.scrollbarXLeft * (i.contentWidth - i.containerWidth) / (i.containerWidth - (i.railXRatio * i.scrollbarXWidth))) - i.negativeScrollAdjustment;
-    updateScroll(element, 'left', scrollLeft);
-  }
-
-  var mouseMoveHandler = function (e) {
-    updateScrollLeft(e.pageX - currentPageX);
-    updateGeometry(element);
-    e.stopPropagation();
-    e.preventDefault();
-  };
-
-  var mouseUpHandler = function () {
-    _.stopScrolling(element, 'x');
-    i.event.unbind(i.ownerDocument, 'mousemove', mouseMoveHandler);
-  };
-
-  i.event.bind(i.scrollbarX, 'mousedown', function (e) {
-    currentPageX = e.pageX;
-    currentLeft = _.toInt(dom.css(i.scrollbarX, 'left')) * i.railXRatio;
-    _.startScrolling(element, 'x');
-
-    i.event.bind(i.ownerDocument, 'mousemove', mouseMoveHandler);
-    i.event.once(i.ownerDocument, 'mouseup', mouseUpHandler);
-
-    e.stopPropagation();
-    e.preventDefault();
-  });
-}
-
-function bindMouseScrollYHandler(element, i) {
-  var currentTop = null;
-  var currentPageY = null;
-
-  function updateScrollTop(deltaY) {
-    var newTop = currentTop + (deltaY * i.railYRatio);
-    var maxTop = Math.max(0, i.scrollbarYRail.getBoundingClientRect().top) + (i.railYRatio * (i.railYHeight - i.scrollbarYHeight));
-
-    if (newTop < 0) {
-      i.scrollbarYTop = 0;
-    } else if (newTop > maxTop) {
-      i.scrollbarYTop = maxTop;
-    } else {
-      i.scrollbarYTop = newTop;
-    }
-
-    var scrollTop = _.toInt(i.scrollbarYTop * (i.contentHeight - i.containerHeight) / (i.containerHeight - (i.railYRatio * i.scrollbarYHeight)));
-    updateScroll(element, 'top', scrollTop);
-  }
-
-  var mouseMoveHandler = function (e) {
-    updateScrollTop(e.pageY - currentPageY);
-    updateGeometry(element);
-    e.stopPropagation();
-    e.preventDefault();
-  };
-
-  var mouseUpHandler = function () {
-    _.stopScrolling(element, 'y');
-    i.event.unbind(i.ownerDocument, 'mousemove', mouseMoveHandler);
-  };
-
-  i.event.bind(i.scrollbarY, 'mousedown', function (e) {
-    currentPageY = e.pageY;
-    currentTop = _.toInt(dom.css(i.scrollbarY, 'top')) * i.railYRatio;
-    _.startScrolling(element, 'y');
-
-    i.event.bind(i.ownerDocument, 'mousemove', mouseMoveHandler);
-    i.event.once(i.ownerDocument, 'mouseup', mouseUpHandler);
-
-    e.stopPropagation();
-    e.preventDefault();
-  });
-}
-
-module.exports = function (element) {
-  var i = instances.get(element);
-  bindMouseScrollXHandler(element, i);
-  bindMouseScrollYHandler(element, i);
-};
-
-},{"../../lib/dom":3,"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],12:[function(require,module,exports){
-'use strict';
-
-var _ = require('../../lib/helper');
-var dom = require('../../lib/dom');
-var instances = require('../instances');
-var updateGeometry = require('../update-geometry');
-var updateScroll = require('../update-scroll');
-
-function bindKeyboardHandler(element, i) {
-  var hovered = false;
-  i.event.bind(element, 'mouseenter', function () {
-    hovered = true;
-  });
-  i.event.bind(element, 'mouseleave', function () {
-    hovered = false;
-  });
-
-  var shouldPrevent = false;
-  function shouldPreventDefault(deltaX, deltaY) {
-    var scrollTop = element.scrollTop;
-    if (deltaX === 0) {
-      if (!i.scrollbarYActive) {
-        return false;
-      }
-      if ((scrollTop === 0 && deltaY > 0) || (scrollTop >= i.contentHeight - i.containerHeight && deltaY < 0)) {
-        return !i.settings.wheelPropagation;
-      }
-    }
-
-    var scrollLeft = element.scrollLeft;
-    if (deltaY === 0) {
-      if (!i.scrollbarXActive) {
-        return false;
-      }
-      if ((scrollLeft === 0 && deltaX < 0) || (scrollLeft >= i.contentWidth - i.containerWidth && deltaX > 0)) {
-        return !i.settings.wheelPropagation;
-      }
-    }
-    return true;
-  }
-
-  i.event.bind(i.ownerDocument, 'keydown', function (e) {
-    if ((e.isDefaultPrevented && e.isDefaultPrevented()) || e.defaultPrevented) {
-      return;
-    }
-
-    var focused = dom.matches(i.scrollbarX, ':focus') ||
-                  dom.matches(i.scrollbarY, ':focus');
-
-    if (!hovered && !focused) {
-      return;
-    }
-
-    var activeElement = document.activeElement ? document.activeElement : i.ownerDocument.activeElement;
-    if (activeElement) {
-      if (activeElement.tagName === 'IFRAME') {
-        activeElement = activeElement.contentDocument.activeElement;
-      } else {
-        // go deeper if element is a webcomponent
-        while (activeElement.shadowRoot) {
-          activeElement = activeElement.shadowRoot.activeElement;
-        }
-      }
-      if (_.isEditable(activeElement)) {
-        return;
-      }
-    }
-
-    var deltaX = 0;
-    var deltaY = 0;
-
-    switch (e.which) {
-    case 37: // left
-      if (e.metaKey) {
-        deltaX = -i.contentWidth;
-      } else if (e.altKey) {
-        deltaX = -i.containerWidth;
-      } else {
-        deltaX = -30;
-      }
-      break;
-    case 38: // up
-      if (e.metaKey) {
-        deltaY = i.contentHeight;
-      } else if (e.altKey) {
-        deltaY = i.containerHeight;
-      } else {
-        deltaY = 30;
-      }
-      break;
-    case 39: // right
-      if (e.metaKey) {
-        deltaX = i.contentWidth;
-      } else if (e.altKey) {
-        deltaX = i.containerWidth;
-      } else {
-        deltaX = 30;
-      }
-      break;
-    case 40: // down
-      if (e.metaKey) {
-        deltaY = -i.contentHeight;
-      } else if (e.altKey) {
-        deltaY = -i.containerHeight;
-      } else {
-        deltaY = -30;
-      }
-      break;
-    case 33: // page up
-      deltaY = 90;
-      break;
-    case 32: // space bar
-      if (e.shiftKey) {
-        deltaY = 90;
-      } else {
-        deltaY = -90;
-      }
-      break;
-    case 34: // page down
-      deltaY = -90;
-      break;
-    case 35: // end
-      if (e.ctrlKey) {
-        deltaY = -i.contentHeight;
-      } else {
-        deltaY = -i.containerHeight;
-      }
-      break;
-    case 36: // home
-      if (e.ctrlKey) {
-        deltaY = element.scrollTop;
-      } else {
-        deltaY = i.containerHeight;
-      }
-      break;
-    default:
-      return;
-    }
-
-    updateScroll(element, 'top', element.scrollTop - deltaY);
-    updateScroll(element, 'left', element.scrollLeft + deltaX);
-    updateGeometry(element);
-
-    shouldPrevent = shouldPreventDefault(deltaX, deltaY);
-    if (shouldPrevent) {
-      e.preventDefault();
-    }
-  });
-}
-
-module.exports = function (element) {
-  var i = instances.get(element);
-  bindKeyboardHandler(element, i);
-};
-
-},{"../../lib/dom":3,"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],13:[function(require,module,exports){
-'use strict';
-
-var instances = require('../instances');
-var updateGeometry = require('../update-geometry');
-var updateScroll = require('../update-scroll');
-
-function bindMouseWheelHandler(element, i) {
-  var shouldPrevent = false;
-
-  function shouldPreventDefault(deltaX, deltaY) {
-    var scrollTop = element.scrollTop;
-    if (deltaX === 0) {
-      if (!i.scrollbarYActive) {
-        return false;
-      }
-      if ((scrollTop === 0 && deltaY > 0) || (scrollTop >= i.contentHeight - i.containerHeight && deltaY < 0)) {
-        return !i.settings.wheelPropagation;
-      }
-    }
-
-    var scrollLeft = element.scrollLeft;
-    if (deltaY === 0) {
-      if (!i.scrollbarXActive) {
-        return false;
-      }
-      if ((scrollLeft === 0 && deltaX < 0) || (scrollLeft >= i.contentWidth - i.containerWidth && deltaX > 0)) {
-        return !i.settings.wheelPropagation;
-      }
-    }
-    return true;
-  }
-
-  function getDeltaFromEvent(e) {
-    var deltaX = e.deltaX;
-    var deltaY = -1 * e.deltaY;
-
-    if (typeof deltaX === "undefined" || typeof deltaY === "undefined") {
-      // OS X Safari
-      deltaX = -1 * e.wheelDeltaX / 6;
-      deltaY = e.wheelDeltaY / 6;
-    }
-
-    if (e.deltaMode && e.deltaMode === 1) {
-      // Firefox in deltaMode 1: Line scrolling
-      deltaX *= 10;
-      deltaY *= 10;
-    }
-
-    if (deltaX !== deltaX && deltaY !== deltaY/* NaN checks */) {
-      // IE in some mouse drivers
-      deltaX = 0;
-      deltaY = e.wheelDelta;
-    }
-
-    if (e.shiftKey) {
-      // reverse axis with shift key
-      return [-deltaY, -deltaX];
-    }
-    return [deltaX, deltaY];
-  }
-
-  function shouldBeConsumedByChild(deltaX, deltaY) {
-    var child = element.querySelector('textarea:hover, select[multiple]:hover, .ps-child:hover');
-    if (child) {
-      if (!window.getComputedStyle(child).overflow.match(/(scroll|auto)/)) {
-        // if not scrollable
-        return false;
-      }
-
-      var maxScrollTop = child.scrollHeight - child.clientHeight;
-      if (maxScrollTop > 0) {
-        if (!(child.scrollTop === 0 && deltaY > 0) && !(child.scrollTop === maxScrollTop && deltaY < 0)) {
-          return true;
-        }
-      }
-      var maxScrollLeft = child.scrollLeft - child.clientWidth;
-      if (maxScrollLeft > 0) {
-        if (!(child.scrollLeft === 0 && deltaX < 0) && !(child.scrollLeft === maxScrollLeft && deltaX > 0)) {
-          return true;
-        }
-      }
-    }
-    return false;
-  }
-
-  function mousewheelHandler(e) {
-    var delta = getDeltaFromEvent(e);
-
-    var deltaX = delta[0];
-    var deltaY = delta[1];
-
-    if (shouldBeConsumedByChild(deltaX, deltaY)) {
-      return;
-    }
-
-    shouldPrevent = false;
-    if (!i.settings.useBothWheelAxes) {
-      // deltaX will only be used for horizontal scrolling and deltaY will
-      // only be used for vertical scrolling - this is the default
-      updateScroll(element, 'top', element.scrollTop - (deltaY * i.settings.wheelSpeed));
-      updateScroll(element, 'left', element.scrollLeft + (deltaX * i.settings.wheelSpeed));
-    } else if (i.scrollbarYActive && !i.scrollbarXActive) {
-      // only vertical scrollbar is active and useBothWheelAxes option is
-      // active, so let's scroll vertical bar using both mouse wheel axes
-      if (deltaY) {
-        updateScroll(element, 'top', element.scrollTop - (deltaY * i.settings.wheelSpeed));
-      } else {
-        updateScroll(element, 'top', element.scrollTop + (deltaX * i.settings.wheelSpeed));
-      }
-      shouldPrevent = true;
-    } else if (i.scrollbarXActive && !i.scrollbarYActive) {
-      // useBothWheelAxes and only horizontal bar is active, so use both
-      // wheel axes for horizontal bar
-      if (deltaX) {
-        updateScroll(element, 'left', element.scrollLeft + (deltaX * i.settings.wheelSpeed));
-      } else {
-        updateScroll(element, 'left', element.scrollLeft - (deltaY * i.settings.wheelSpeed));
-      }
-      shouldPrevent = true;
-    }
-
-    updateGeometry(element);
-
-    shouldPrevent = (shouldPrevent || shouldPreventDefault(deltaX, deltaY));
-    if (shouldPrevent) {
-      e.stopPropagation();
-      e.preventDefault();
-    }
-  }
-
-  if (typeof window.onwheel !== "undefined") {
-    i.event.bind(element, 'wheel', mousewheelHandler);
-  } else if (typeof window.onmousewheel !== "undefined") {
-    i.event.bind(element, 'mousewheel', mousewheelHandler);
-  }
-}
-
-module.exports = function (element) {
-  var i = instances.get(element);
-  bindMouseWheelHandler(element, i);
-};
-
-},{"../instances":18,"../update-geometry":19,"../update-scroll":20}],14:[function(require,module,exports){
-'use strict';
-
-var instances = require('../instances');
-var updateGeometry = require('../update-geometry');
-
-function bindNativeScrollHandler(element, i) {
-  i.event.bind(element, 'scroll', function () {
-    updateGeometry(element);
-  });
-}
-
-module.exports = function (element) {
-  var i = instances.get(element);
-  bindNativeScrollHandler(element, i);
-};
-
-},{"../instances":18,"../update-geometry":19}],15:[function(require,module,exports){
-'use strict';
-
-var _ = require('../../lib/helper');
-var instances = require('../instances');
-var updateGeometry = require('../update-geometry');
-var updateScroll = require('../update-scroll');
-
-function bindSelectionHandler(element, i) {
-  function getRangeNode() {
-    var selection = window.getSelection ? window.getSelection() :
-                    document.getSelection ? document.getSelection() : '';
-    if (selection.toString().length === 0) {
-      return null;
-    } else {
-      return selection.getRangeAt(0).commonAncestorContainer;
-    }
-  }
-
-  var scrollingLoop = null;
-  var scrollDiff = {top: 0, left: 0};
-  function startScrolling() {
-    if (!scrollingLoop) {
-      scrollingLoop = setInterval(function () {
-        if (!instances.get(element)) {
-          clearInterval(scrollingLoop);
-          return;
-        }
-
-        updateScroll(element, 'top', element.scrollTop + scrollDiff.top);
-        updateScroll(element, 'left', element.scrollLeft + scrollDiff.left);
-        updateGeometry(element);
-      }, 50); // every .1 sec
-    }
-  }
-  function stopScrolling() {
-    if (scrollingLoop) {
-      clearInterval(scrollingLoop);
-      scrollingLoop = null;
-    }
-    _.stopScrolling(element);
-  }
-
-  var isSelected = false;
-  i.event.bind(i.ownerDocument, 'selectionchange', function () {
-    if (element.contains(getRangeNode())) {
-      isSelected = true;
-    } else {
-      isSelected = false;
-      stopScrolling();
-    }
-  });
-  i.event.bind(window, 'mouseup', function () {
-    if (isSelected) {
-      isSelected = false;
-      stopScrolling();
-    }
-  });
-  i.event.bind(window, 'keyup', function () {
-    if (isSelected) {
-      isSelected = false;
-      stopScrolling();
-    }
-  });
-
-  i.event.bind(window, 'mousemove', function (e) {
-    if (isSelected) {
-      var mousePosition = {x: e.pageX, y: e.pageY};
-      var containerGeometry = {
-        left: element.offsetLeft,
-        right: element.offsetLeft + element.offsetWidth,
-        top: element.offsetTop,
-        bottom: element.offsetTop + element.offsetHeight
-      };
-
-      if (mousePosition.x < containerGeometry.left + 3) {
-        scrollDiff.left = -5;
-        _.startScrolling(element, 'x');
-      } else if (mousePosition.x > containerGeometry.right - 3) {
-        scrollDiff.left = 5;
-        _.startScrolling(element, 'x');
-      } else {
-        scrollDiff.left = 0;
-      }
-
-      if (mousePosition.y < containerGeometry.top + 3) {
-        if (containerGeometry.top + 3 - mousePosition.y < 5) {
-          scrollDiff.top = -5;
-        } else {
-          scrollDiff.top = -20;
-        }
-        _.startScrolling(element, 'y');
-      } else if (mousePosition.y > containerGeometry.bottom - 3) {
-        if (mousePosition.y - containerGeometry.bottom + 3 < 5) {
-          scrollDiff.top = 5;
-        } else {
-          scrollDiff.top = 20;
-        }
-        _.startScrolling(element, 'y');
-      } else {
-        scrollDiff.top = 0;
-      }
-
-      if (scrollDiff.top === 0 && scrollDiff.left === 0) {
-        stopScrolling();
-      } else {
-        startScrolling();
-      }
-    }
-  });
-}
-
-module.exports = function (element) {
-  var i = instances.get(element);
-  bindSelectionHandler(element, i);
-};
-
-},{"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],16:[function(require,module,exports){
-'use strict';
-
-var _ = require('../../lib/helper');
-var instances = require('../instances');
-var updateGeometry = require('../update-geometry');
-var updateScroll = require('../update-scroll');
-
-function bindTouchHandler(element, i, supportsTouch, supportsIePointer) {
-  function shouldPreventDefault(deltaX, deltaY) {
-    var scrollTop = element.scrollTop;
-    var scrollLeft = element.scrollLeft;
-    var magnitudeX = Math.abs(deltaX);
-    var magnitudeY = Math.abs(deltaY);
-
-    if (magnitudeY > magnitudeX) {
-      // user is perhaps trying to swipe up/down the page
-
-      if (((deltaY < 0) && (scrollTop === i.contentHeight - i.containerHeight)) ||
-          ((deltaY > 0) && (scrollTop === 0))) {
-        return !i.settings.swipePropagation;
-      }
-    } else if (magnitudeX > magnitudeY) {
-      // user is perhaps trying to swipe left/right across the page
-
-      if (((deltaX < 0) && (scrollLeft === i.contentWidth - i.containerWidth)) ||
-          ((deltaX > 0) && (scrollLeft === 0))) {
-        return !i.settings.swipePropagation;
-      }
-    }
-
-    return true;
-  }
-
-  function applyTouchMove(differenceX, differenceY) {
-    updateScroll(element, 'top', element.scrollTop - differenceY);
-    updateScroll(element, 'left', element.scrollLeft - differenceX);
-
-    updateGeometry(element);
-  }
-
-  var startOffset = {};
-  var startTime = 0;
-  var speed = {};
-  var easingLoop = null;
-  var inGlobalTouch = false;
-  var inLocalTouch = false;
-
-  function globalTouchStart() {
-    inGlobalTouch = true;
-  }
-  function globalTouchEnd() {
-    inGlobalTouch = false;
-  }
-
-  function getTouch(e) {
-    if (e.targetTouches) {
-      return e.targetTouches[0];
-    } else {
-      // Maybe IE pointer
-      return e;
-    }
-  }
-  function shouldHandle(e) {
-    if (e.targetTouches && e.targetTouches.length === 1) {
-      return true;
-    }
-    if (e.pointerType && e.pointerType !== 'mouse' && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
-      return true;
-    }
-    return false;
-  }
-  function touchStart(e) {
-    if (shouldHandle(e)) {
-      inLocalTouch = true;
-
-      var touch = getTouch(e);
-
-      startOffset.pageX = touch.pageX;
-      startOffset.pageY = touch.pageY;
-
-      startTime = (new Date()).getTime();
-
-      if (easingLoop !== null) {
-        clearInterval(easingLoop);
-      }
-
-      e.stopPropagation();
-    }
-  }
-  function touchMove(e) {
-    if (!inLocalTouch && i.settings.swipePropagation) {
-      touchStart(e);
-    }
-    if (!inGlobalTouch && inLocalTouch && shouldHandle(e)) {
-      var touch = getTouch(e);
-
-      var currentOffset = {pageX: touch.pageX, pageY: touch.pageY};
-
-      var differenceX = currentOffset.pageX - startOffset.pageX;
-      var differenceY = currentOffset.pageY - startOffset.pageY;
-
-      applyTouchMove(differenceX, differenceY);
-      startOffset = currentOffset;
-
-      var currentTime = (new Date()).getTime();
-
-      var timeGap = currentTime - startTime;
-      if (timeGap > 0) {
-        speed.x = differenceX / timeGap;
-        speed.y = differenceY / timeGap;
-        startTime = currentTime;
-      }
-
-      if (shouldPreventDefault(differenceX, differenceY)) {
-        e.stopPropagation();
-        e.preventDefault();
-      }
-    }
-  }
-  function touchEnd() {
-    if (!inGlobalTouch && inLocalTouch) {
-      inLocalTouch = false;
-
-      clearInterval(easingLoop);
-      easingLoop = setInterval(function () {
-        if (!instances.get(element)) {
-          clearInterval(easingLoop);
-          return;
-        }
-
-        if (!speed.x && !speed.y) {
-          clearInterval(easingLoop);
-          return;
-        }
-
-        if (Math.abs(speed.x) < 0.01 && Math.abs(speed.y) < 0.01) {
-          clearInterval(easingLoop);
-          return;
-        }
-
-        applyTouchMove(speed.x * 30, speed.y * 30);
-
-        speed.x *= 0.8;
-        speed.y *= 0.8;
-      }, 10);
-    }
-  }
-
-  if (supportsTouch) {
-    i.event.bind(window, 'touchstart', globalTouchStart);
-    i.event.bind(window, 'touchend', globalTouchEnd);
-    i.event.bind(element, 'touchstart', touchStart);
-    i.event.bind(element, 'touchmove', touchMove);
-    i.event.bind(element, 'touchend', touchEnd);
-  } else if (supportsIePointer) {
-    if (window.PointerEvent) {
-      i.event.bind(window, 'pointerdown', globalTouchStart);
-      i.event.bind(window, 'pointerup', globalTouchEnd);
-      i.event.bind(element, 'pointerdown', touchStart);
-      i.event.bind(element, 'pointermove', touchMove);
-      i.event.bind(element, 'pointerup', touchEnd);
-    } else if (window.MSPointerEvent) {
-      i.event.bind(window, 'MSPointerDown', globalTouchStart);
-      i.event.bind(window, 'MSPointerUp', globalTouchEnd);
-      i.event.bind(element, 'MSPointerDown', touchStart);
-      i.event.bind(element, 'MSPointerMove', touchMove);
-      i.event.bind(element, 'MSPointerUp', touchEnd);
-    }
-  }
-}
-
-module.exports = function (element) {
-  if (!_.env.supportsTouch && !_.env.supportsIePointer) {
-    return;
-  }
-
-  var i = instances.get(element);
-  bindTouchHandler(element, i, _.env.supportsTouch, _.env.supportsIePointer);
-};
-
-},{"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],17:[function(require,module,exports){
-'use strict';
-
-var _ = require('../lib/helper');
-var cls = require('../lib/class');
-var instances = require('./instances');
-var updateGeometry = require('./update-geometry');
-
-// Handlers
-var handlers = {
-  'click-rail': require('./handler/click-rail'),
-  'drag-scrollbar': require('./handler/drag-scrollbar'),
-  'keyboard': require('./handler/keyboard'),
-  'wheel': require('./handler/mouse-wheel'),
-  'touch': require('./handler/touch'),
-  'selection': require('./handler/selection')
-};
-var nativeScrollHandler = require('./handler/native-scroll');
-
-module.exports = function (element, userSettings) {
-  userSettings = typeof userSettings === 'object' ? userSettings : {};
-
-  cls.add(element, 'ps-container');
-
-  // Create a plugin instance.
-  var i = instances.add(element);
-
-  i.settings = _.extend(i.settings, userSettings);
-  cls.add(element, 'ps-theme-' + i.settings.theme);
-
-  i.settings.handlers.forEach(function (handlerName) {
-    handlers[handlerName](element);
-  });
-
-  nativeScrollHandler(element);
-
-  updateGeometry(element);
-};
-
-},{"../lib/class":2,"../lib/helper":6,"./handler/click-rail":10,"./handler/drag-scrollbar":11,"./handler/keyboard":12,"./handler/mouse-wheel":13,"./handler/native-scroll":14,"./handler/selection":15,"./handler/touch":16,"./instances":18,"./update-geometry":19}],18:[function(require,module,exports){
-'use strict';
-
-var _ = require('../lib/helper');
-var cls = require('../lib/class');
-var defaultSettings = require('./default-setting');
-var dom = require('../lib/dom');
-var EventManager = require('../lib/event-manager');
-var guid = require('../lib/guid');
-
-var instances = {};
-
-function Instance(element) {
-  var i = this;
-
-  i.settings = _.clone(defaultSettings);
-  i.containerWidth = null;
-  i.containerHeight = null;
-  i.contentWidth = null;
-  i.contentHeight = null;
-
-  i.isRtl = dom.css(element, 'direction') === "rtl";
-  i.isNegativeScroll = (function () {
-    var originalScrollLeft = element.scrollLeft;
-    var result = null;
-    element.scrollLeft = -1;
-    result = element.scrollLeft < 0;
-    element.scrollLeft = originalScrollLeft;
-    return result;
-  })();
-  i.negativeScrollAdjustment = i.isNegativeScroll ? element.scrollWidth - element.clientWidth : 0;
-  i.event = new EventManager();
-  i.ownerDocument = element.ownerDocument || document;
-
-  function focus() {
-    cls.add(element, 'ps-focus');
-  }
-
-  function blur() {
-    cls.remove(element, 'ps-focus');
-  }
-
-  i.scrollbarXRail = dom.appendTo(dom.e('div', 'ps-scrollbar-x-rail'), element);
-  i.scrollbarX = dom.appendTo(dom.e('div', 'ps-scrollbar-x'), i.scrollbarXRail);
-  i.scrollbarX.setAttribute('tabindex', 0);
-  i.event.bind(i.scrollbarX, 'focus', focus);
-  i.event.bind(i.scrollbarX, 'blur', blur);
-  i.scrollbarXActive = null;
-  i.scrollbarXWidth = null;
-  i.scrollbarXLeft = null;
-  i.scrollbarXBottom = _.toInt(dom.css(i.scrollbarXRail, 'bottom'));
-  i.isScrollbarXUsingBottom = i.scrollbarXBottom === i.scrollbarXBottom; // !isNaN
-  i.scrollbarXTop = i.isScrollbarXUsingBottom ? null : _.toInt(dom.css(i.scrollbarXRail, 'top'));
-  i.railBorderXWidth = _.toInt(dom.css(i.scrollbarXRail, 'borderLeftWidth')) + _.toInt(dom.css(i.scrollbarXRail, 'borderRightWidth'));
-  // Set rail to display:block to calculate margins
-  dom.css(i.scrollbarXRail, 'display', 'block');
-  i.railXMarginWidth = _.toInt(dom.css(i.scrollbarXRail, 'marginLeft')) + _.toInt(dom.css(i.scrollbarXRail, 'marginRight'));
-  dom.css(i.scrollbarXRail, 'display', '');
-  i.railXWidth = null;
-  i.railXRatio = null;
-
-  i.scrollbarYRail = dom.appendTo(dom.e('div', 'ps-scrollbar-y-rail'), element);
-  i.scrollbarY = dom.appendTo(dom.e('div', 'ps-scrollbar-y'), i.scrollbarYRail);
-  i.scrollbarY.setAttribute('tabindex', 0);
-  i.event.bind(i.scrollbarY, 'focus', focus);
-  i.event.bind(i.scrollbarY, 'blur', blur);
-  i.scrollbarYActive = null;
-  i.scrollbarYHeight = null;
-  i.scrollbarYTop = null;
-  i.scrollbarYRight = _.toInt(dom.css(i.scrollbarYRail, 'right'));
-  i.isScrollbarYUsingRight = i.scrollbarYRight === i.scrollbarYRight; // !isNaN
-  i.scrollbarYLeft = i.isScrollbarYUsingRight ? null : _.toInt(dom.css(i.scrollbarYRail, 'left'));
-  i.scrollbarYOuterWidth = i.isRtl ? _.outerWidth(i.scrollbarY) : null;
-  i.railBorderYWidth = _.toInt(dom.css(i.scrollbarYRail, 'borderTopWidth')) + _.toInt(dom.css(i.scrollbarYRail, 'borderBottomWidth'));
-  dom.css(i.scrollbarYRail, 'display', 'block');
-  i.railYMarginHeight = _.toInt(dom.css(i.scrollbarYRail, 'marginTop')) + _.toInt(dom.css(i.scrollbarYRail, 'marginBottom'));
-  dom.css(i.scrollbarYRail, 'display', '');
-  i.railYHeight = null;
-  i.railYRatio = null;
-}
-
-function getId(element) {
-  return element.getAttribute('data-ps-id');
-}
-
-function setId(element, id) {
-  element.setAttribute('data-ps-id', id);
-}
-
-function removeId(element) {
-  element.removeAttribute('data-ps-id');
-}
-
-exports.add = function (element) {
-  var newId = guid();
-  setId(element, newId);
-  instances[newId] = new Instance(element);
-  return instances[newId];
-};
-
-exports.remove = function (element) {
-  delete instances[getId(element)];
-  removeId(element);
-};
-
-exports.get = function (element) {
-  return instances[getId(element)];
-};
-
-},{"../lib/class":2,"../lib/dom":3,"../lib/event-manager":4,"../lib/guid":5,"../lib/helper":6,"./default-setting":8}],19:[function(require,module,exports){
-'use strict';
-
-var _ = require('../lib/helper');
-var cls = require('../lib/class');
-var dom = require('../lib/dom');
-var instances = require('./instances');
-var updateScroll = require('./update-scroll');
-
-function getThumbSize(i, thumbSize) {
-  if (i.settings.minScrollbarLength) {
-    thumbSize = Math.max(thumbSize, i.settings.minScrollbarLength);
-  }
-  if (i.settings.maxScrollbarLength) {
-    thumbSize = Math.min(thumbSize, i.settings.maxScrollbarLength);
-  }
-  return thumbSize;
-}
-
-function updateCss(element, i) {
-  var xRailOffset = {width: i.railXWidth};
-  if (i.isRtl) {
-    xRailOffset.left = i.negativeScrollAdjustment + element.scrollLeft + i.containerWidth - i.contentWidth;
-  } else {
-    xRailOffset.left = element.scrollLeft;
-  }
-  if (i.isScrollbarXUsingBottom) {
-    xRailOffset.bottom = i.scrollbarXBottom - element.scrollTop;
-  } else {
-    xRailOffset.top = i.scrollbarXTop + element.scrollTop;
-  }
-  dom.css(i.scrollbarXRail, xRailOffset);
-
-  var yRailOffset = {top: element.scrollTop, height: i.railYHeight};
-  if (i.isScrollbarYUsingRight) {
-    if (i.isRtl) {
-      yRailOffset.right = i.contentWidth - (i.negativeScrollAdjustment + element.scrollLeft) - i.scrollbarYRight - i.scrollbarYOuterWidth;
-    } else {
-      yRailOffset.right = i.scrollbarYRight - element.scrollLeft;
-    }
-  } else {
-    if (i.isRtl) {
-      yRailOffset.left = i.negativeScrollAdjustment + element.scrollLeft + i.containerWidth * 2 - i.contentWidth - i.scrollbarYLeft - i.scrollbarYOuterWidth;
-    } else {
-      yRailOffset.left = i.scrollbarYLeft + element.scrollLeft;
-    }
-  }
-  dom.css(i.scrollbarYRail, yRailOffset);
-
-  dom.css(i.scrollbarX, {left: i.scrollbarXLeft, width: i.scrollbarXWidth - i.railBorderXWidth});
-  dom.css(i.scrollbarY, {top: i.scrollbarYTop, height: i.scrollbarYHeight - i.railBorderYWidth});
-}
-
-module.exports = function (element) {
-  var i = instances.get(element);
-
-  i.containerWidth = element.clientWidth;
-  i.containerHeight = element.clientHeight;
-  i.contentWidth = element.scrollWidth;
-  i.contentHeight = element.scrollHeight;
-
-  var existingRails;
-  if (!element.contains(i.scrollbarXRail)) {
-    existingRails = dom.queryChildren(element, '.ps-scrollbar-x-rail');
-    if (existingRails.length > 0) {
-      existingRails.forEach(function (rail) {
-        dom.remove(rail);
-      });
-    }
-    dom.appendTo(i.scrollbarXRail, element);
-  }
-  if (!element.contains(i.scrollbarYRail)) {
-    existingRails = dom.queryChildren(element, '.ps-scrollbar-y-rail');
-    if (existingRails.length > 0) {
-      existingRails.forEach(function (rail) {
-        dom.remove(rail);
-      });
-    }
-    dom.appendTo(i.scrollbarYRail, element);
-  }
-
-  if (!i.settings.suppressScrollX && i.containerWidth + i.settings.scrollXMarginOffset < i.contentWidth) {
-    i.scrollbarXActive = true;
-    i.railXWidth = i.containerWidth - i.railXMarginWidth;
-    i.railXRatio = i.containerWidth / i.railXWidth;
-    i.scrollbarXWidth = getThumbSize(i, _.toInt(i.railXWidth * i.containerWidth / i.contentWidth));
-    i.scrollbarXLeft = _.toInt((i.negativeScrollAdjustment + element.scrollLeft) * (i.railXWidth - i.scrollbarXWidth) / (i.contentWidth - i.containerWidth));
-  } else {
-    i.scrollbarXActive = false;
-  }
-
-  if (!i.settings.suppressScrollY && i.containerHeight + i.settings.scrollYMarginOffset < i.contentHeight) {
-    i.scrollbarYActive = true;
-    i.railYHeight = i.containerHeight - i.railYMarginHeight;
-    i.railYRatio = i.containerHeight / i.railYHeight;
-    i.scrollbarYHeight = getThumbSize(i, _.toInt(i.railYHeight * i.containerHeight / i.contentHeight));
-    i.scrollbarYTop = _.toInt(element.scrollTop * (i.railYHeight - i.scrollbarYHeight) / (i.contentHeight - i.containerHeight));
-  } else {
-    i.scrollbarYActive = false;
-  }
-
-  if (i.scrollbarXLeft >= i.railXWidth - i.scrollbarXWidth) {
-    i.scrollbarXLeft = i.railXWidth - i.scrollbarXWidth;
-  }
-  if (i.scrollbarYTop >= i.railYHeight - i.scrollbarYHeight) {
-    i.scrollbarYTop = i.railYHeight - i.scrollbarYHeight;
-  }
-
-  updateCss(element, i);
-
-  if (i.scrollbarXActive) {
-    cls.add(element, 'ps-active-x');
-  } else {
-    cls.remove(element, 'ps-active-x');
-    i.scrollbarXWidth = 0;
-    i.scrollbarXLeft = 0;
-    updateScroll(element, 'left', 0);
-  }
-  if (i.scrollbarYActive) {
-    cls.add(element, 'ps-active-y');
-  } else {
-    cls.remove(element, 'ps-active-y');
-    i.scrollbarYHeight = 0;
-    i.scrollbarYTop = 0;
-    updateScroll(element, 'top', 0);
-  }
-};
-
-},{"../lib/class":2,"../lib/dom":3,"../lib/helper":6,"./instances":18,"./update-scroll":20}],20:[function(require,module,exports){
-'use strict';
-
-var instances = require('./instances');
-
-var lastTop;
-var lastLeft;
-
-var createDOMEvent = function (name) {
-  var event = document.createEvent("Event");
-  event.initEvent(name, true, true);
-  return event;
-};
-
-module.exports = function (element, axis, value) {
-  if (typeof element === 'undefined') {
-    throw 'You must provide an element to the update-scroll function';
-  }
-
-  if (typeof axis === 'undefined') {
-    throw 'You must provide an axis to the update-scroll function';
-  }
-
-  if (typeof value === 'undefined') {
-    throw 'You must provide a value to the update-scroll function';
-  }
-
-  if (axis === 'top' && value <= 0) {
-    element.scrollTop = value = 0; // don't allow negative scroll
-    element.dispatchEvent(createDOMEvent('ps-y-reach-start'));
-  }
-
-  if (axis === 'left' && value <= 0) {
-    element.scrollLeft = value = 0; // don't allow negative scroll
-    element.dispatchEvent(createDOMEvent('ps-x-reach-start'));
-  }
-
-  var i = instances.get(element);
-
-  if (axis === 'top' && value >= i.contentHeight - i.containerHeight) {
-    // don't allow scroll past container
-    value = i.contentHeight - i.containerHeight;
-    if (value - element.scrollTop <= 1) {
-      // mitigates rounding errors on non-subpixel scroll values
-      value = element.scrollTop;
-    } else {
-      element.scrollTop = value;
-    }
-    element.dispatchEvent(createDOMEvent('ps-y-reach-end'));
-  }
-
-  if (axis === 'left' && value >= i.contentWidth - i.containerWidth) {
-    // don't allow scroll past container
-    value = i.contentWidth - i.containerWidth;
-    if (value - element.scrollLeft <= 1) {
-      // mitigates rounding errors on non-subpixel scroll values
-      value = element.scrollLeft;
-    } else {
-      element.scrollLeft = value;
-    }
-    element.dispatchEvent(createDOMEvent('ps-x-reach-end'));
-  }
-
-  if (!lastTop) {
-    lastTop = element.scrollTop;
-  }
-
-  if (!lastLeft) {
-    lastLeft = element.scrollLeft;
-  }
-
-  if (axis === 'top' && value < lastTop) {
-    element.dispatchEvent(createDOMEvent('ps-scroll-up'));
-  }
-
-  if (axis === 'top' && value > lastTop) {
-    element.dispatchEvent(createDOMEvent('ps-scroll-down'));
-  }
-
-  if (axis === 'left' && value < lastLeft) {
-    element.dispatchEvent(createDOMEvent('ps-scroll-left'));
-  }
-
-  if (axis === 'left' && value > lastLeft) {
-    element.dispatchEvent(createDOMEvent('ps-scroll-right'));
-  }
-
-  if (axis === 'top') {
-    element.scrollTop = lastTop = value;
-    element.dispatchEvent(createDOMEvent('ps-scroll-y'));
-  }
-
-  if (axis === 'left') {
-    element.scrollLeft = lastLeft = value;
-    element.dispatchEvent(createDOMEvent('ps-scroll-x'));
-  }
-
-};
-
-},{"./instances":18}],21:[function(require,module,exports){
-'use strict';
-
-var _ = require('../lib/helper');
-var dom = require('../lib/dom');
-var instances = require('./instances');
-var updateGeometry = require('./update-geometry');
-var updateScroll = require('./update-scroll');
-
-module.exports = function (element) {
-  var i = instances.get(element);
-
-  if (!i) {
-    return;
-  }
-
-  // Recalcuate negative scrollLeft adjustment
-  i.negativeScrollAdjustment = i.isNegativeScroll ? element.scrollWidth - element.clientWidth : 0;
-
-  // Recalculate rail margins
-  dom.css(i.scrollbarXRail, 'display', 'block');
-  dom.css(i.scrollbarYRail, 'display', 'block');
-  i.railXMarginWidth = _.toInt(dom.css(i.scrollbarXRail, 'marginLeft')) + _.toInt(dom.css(i.scrollbarXRail, 'marginRight'));
-  i.railYMarginHeight = _.toInt(dom.css(i.scrollbarYRail, 'marginTop')) + _.toInt(dom.css(i.scrollbarYRail, 'marginBottom'));
-
-  // Hide scrollbars not to affect scrollWidth and scrollHeight
-  dom.css(i.scrollbarXRail, 'display', 'none');
-  dom.css(i.scrollbarYRail, 'display', 'none');
-
-  updateGeometry(element);
-
-  // Update top/left scroll to trigger events
-  updateScroll(element, 'top', element.scrollTop);
-  updateScroll(element, 'left', element.scrollLeft);
-
-  dom.css(i.scrollbarXRail, 'display', '');
-  dom.css(i.scrollbarYRail, 'display', '');
-};
-
-},{"../lib/dom":3,"../lib/helper":6,"./instances":18,"./update-geometry":19,"./update-scroll":20}]},{},[1]);
-
-/**
- * Provides utility functions for date operations.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Date/Util
- */
-define('WoltLabSuite/Core/Date/Util',['Language'], function(Language) {
-       "use strict";
-       
-       /**
-        * @exports     WoltLabSuite/Core/Date/Util
-        */
-       var DateUtil = {
-               /**
-                * Returns the formatted date.
-                * 
-                * @param       {Date}          date            date object
-                * @returns     {string}        formatted date
-                */
-               formatDate: function(date) {
-                       return this.format(date, Language.get('wcf.date.dateFormat'));
-               },
-               
-               /**
-                * Returns the formatted time.
-                * 
-                * @param       {Date}          date            date object
-                * @returns     {string}        formatted time
-                */
-               formatTime: function(date) {
-                       return this.format(date, Language.get('wcf.date.timeFormat'));
-               },
-               
-               /**
-                * Returns the formatted date time.
-                * 
-                * @param       {Date}          date            date object
-                * @returns     {string}        formatted date time
-                */
-               formatDateTime: function(date) {
-                       return this.format(date, Language.get('wcf.date.dateTimeFormat').replace(/%date%/, Language.get('wcf.date.dateFormat')).replace(/%time%/, Language.get('wcf.date.timeFormat')));
-               },
-               
-               /**
-                * Formats a date using PHP's `date()` modifiers.
-                * 
-                * @param       {Date}          date            date object
-                * @param       {string}        format          output format
-                * @returns     {string}        formatted date
-                */
-               format: function(date, format) {
-                       var char;
-                       var out = '';
-                       
-                       // ISO 8601 date, best recognition by PHP's strtotime()
-                       if (format === 'c') {
-                               format = 'Y-m-dTH:i:sP';
-                       }
-                       
-                       for (var i = 0, length = format.length; i < length; i++) {
-                               switch (format[i]) {
-                                       // seconds
-                                       case 's':
-                                               // `00` through `59`
-                                               char = ('0' + date.getSeconds().toString()).slice(-2);
-                                               break;
-                                       
-                                       // minutes
-                                       case 'i':
-                                               // `00` through `59`
-                                               char = date.getMinutes();
-                                               if (char < 10) char = "0" + char;
-                                               break;
-                                       
-                                       // hours
-                                       case 'a':
-                                               // `am` or `pm`
-                                               char = (date.getHours() > 11) ? 'pm' : 'am';
-                                               break;
-                                       case 'g':
-                                               // `1` through `12`
-                                               char = date.getHours();
-                                               if (char === 0) char = 12;
-                                               else if (char > 12) char -= 12;
-                                               break;
-                                       case 'h':
-                                               // `01` through `12`
-                                               char = date.getHours();
-                                               if (char === 0) char = 12;
-                                               else if (char > 12) char -= 12;
-                                               
-                                               char = ('0' + char.toString()).slice(-2);
-                                               break;
-                                       case 'A':
-                                               // `AM` or `PM`
-                                               char = (date.getHours() > 11) ? 'PM' : 'AM';
-                                               break;
-                                       case 'G':
-                                               // `0` through `23`
-                                               char = date.getHours();
-                                               break;
-                                       case 'H':
-                                               // `00` through `23`
-                                               char = date.getHours();
-                                               char = ('0' + char.toString()).slice(-2);
-                                               break;
-                                       
-                                       // day
-                                       case 'd':
-                                               // `01` through `31`
-                                               char = date.getDate();
-                                               char = ('0' + char.toString()).slice(-2);
-                                               break;
-                                       case 'j':
-                                               // `1` through `31`
-                                               char = date.getDate();
-                                               break;
-                                       case 'l':
-                                               // `Monday` through `Sunday` (localized)
-                                               char = Language.get('__days')[date.getDay()];
-                                               break;
-                                       case 'D':
-                                               // `Mon` through `Sun` (localized)
-                                               char = Language.get('__daysShort')[date.getDay()];
-                                               break;
-                                       case 'S':
-                                               // ignore english ordinal suffix
-                                               char = '';
-                                               break;
-                                       
-                                       // month
-                                       case 'm':
-                                               // `01` through `12`
-                                               char = date.getMonth() + 1;
-                                               char = ('0' + char.toString()).slice(-2);
-                                               break;
-                                       case 'n':
-                                               // `1` through `12`
-                                               char = date.getMonth() + 1;
-                                               break;
-                                       case 'F':
-                                               // `January` through `December` (localized)
-                                               char = Language.get('__months')[date.getMonth()];
-                                               break;
-                                       case 'M':
-                                               // `Jan` through `Dec` (localized)
-                                               char = Language.get('__monthsShort')[date.getMonth()];
-                                               break;
-                                       
-                                       // year
-                                       case 'y':
-                                               // `00` through `99`
-                                               char = date.getYear().toString().replace(/^\d{2}/, '');
-                                               break;
-                                       case 'Y':
-                                               // Examples: `1988` or `2015`
-                                               char = date.getFullYear();
-                                               break;
-                                       
-                                       // timezone
-                                       case 'P':
-                                               var offset = date.getTimezoneOffset();
-                                               char = (offset > 0) ? '-' : '+';
-                                               
-                                               offset = Math.abs(offset);
-                                               
-                                               char += ('0' + (~~(offset / 60)).toString()).slice(-2);
-                                               char += ':';
-                                               char += ('0' + (offset % 60).toString()).slice(-2);
-                                               
-                                               break;
-                                               
-                                       // specials
-                                       case 'r':
-                                               char = date.toString();
-                                               break;
-                                       case 'U':
-                                               char = Math.round(date.getTime() / 1000);
-                                               break;
-                                               
-                                       // escape sequence
-                                       case '\\':
-                                               char = '';
-                                               if (i + 1 < length) {
-                                                       char = format[++i];
-                                               }
-                                               break;
-                                       
-                                       default:
-                                               char = format[i];
-                                               break;
-                               }
-                               
-                               out += char;
-                       }
-                       
-                       return out;
-               },
-               
-               /**
-                * Returns UTC timestamp, if date is not given, current time will be used.
-                * 
-                * @param       {Date}          date    target date
-                * @return      {int}           UTC timestamp in seconds
-                */
-               gmdate: function(date) {
-                       if (!(date instanceof Date)) {
-                               date = new Date();
-                       }
-                       
-                       return Math.round(Date.UTC(
-                               date.getUTCFullYear(),
-                               date.getUTCMonth(),
-                               date.getUTCDay(),
-                               date.getUTCHours(),
-                               date.getUTCMinutes(),
-                               date.getUTCSeconds()
-                       ) / 1000);
-               },
-               
-               /**
-                * Returns a `time` element based on the given date just like a `time`
-                * element created by `wcf\system\template\plugin\TimeModifierTemplatePlugin`.
-                * 
-                * Note: The actual content of the element is empty and is expected
-                * to be automatically updated by `WoltLabSuite/Core/Date/Time/Relative`
-                * (for dates not in the future) after the DOM change listener has been triggered.
-                * 
-                * @param       {Date}          date    displayed date
-                * @return      {HTMLElement}   `time` element
-                */
-               getTimeElement: function(date) {
-                       var time = elCreate('time');
-                       time.className = 'datetime';
-                       
-                       var formattedDate = this.formatDate(date);
-                       var formattedTime = this.formatTime(date);
-                       
-                       elAttr(time, 'datetime', this.format(date, 'c'));
-                       elData(time, 'timestamp', (date.getTime() - date.getMilliseconds()) / 1000);
-                       elData(time, 'date', formattedDate);
-                       elData(time, 'time', formattedTime);
-                       elData(time, 'offset', date.getTimezoneOffset() * 60); // PHP returns minutes, JavaScript returns seconds
-                       
-                       if (date.getTime() > Date.now()) {
-                               elData(time, 'is-future-date', 'true');
-                               
-                               time.textContent = Language.get('wcf.date.dateTimeFormat').replace('%time%', formattedTime).replace('%date%', formattedDate);
-                       }
-                       
-                       return time;
-               },
-               
-               /**
-                * Returns a Date object with precise offset (including timezone and local timezone).
-                * 
-                * @param       {int}           timestamp       timestamp in milliseconds
-                * @param       {int}           offset          timezone offset in milliseconds
-                * @return      {Date}          localized date
-                */
-               getTimezoneDate: function(timestamp, offset) {
-                       var date = new Date(timestamp);
-                       var localOffset = date.getTimezoneOffset() * 60000;
-                       
-                       return new Date((timestamp + localOffset + offset));
-               }
-       };
-       
-       return DateUtil;
-});
-
-/**
- * Provides an object oriented API on top of `setInterval`.
- * 
- * @author     Tim Duesterhus
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Timer/Repeating
- */
-define('WoltLabSuite/Core/Timer/Repeating',[], function() {
-       "use strict";
-       
-       /**
-        * Creates a new timer that executes the given `callback` every `delta` milliseconds.
-        * It will be created in started mode. Call `stop()` if necessary.
-        * The `callback` will be passed the owning instance of `Repeating`.
-        * 
-        * @constructor
-        * @param       {function(Repeating)}   callback
-        * @param       {int}                   delta
-        */
-       function Repeating(callback, delta) {
-               if (typeof callback !== 'function') {
-                       throw new TypeError("Expected a valid callback as first argument.");
-               }
-               if (delta < 0 || delta > 86400 * 1000) {
-                       throw new RangeError("Invalid delta " + delta + ". Delta must be in the interval [0, 86400000].");
-               }
-               
-               // curry callback with `this` as the first parameter
-               this._callback = callback.bind(undefined, this);
-               
-               this._delta = delta;
-               this._timer = undefined;
-               
-               this.restart();
-       }
-       Repeating.prototype = {
-               /**
-                * Stops the timer and restarts it. The next call will occur in `delta` milliseconds.
-                */
-               restart: function() {
-                       this.stop();
-                       
-                       this._timer = setInterval(this._callback, this._delta);
-               },
-               
-               /**
-                * Stops the timer. It will no longer be called until you call `restart`.
-                */
-               stop: function() {
-                       if (this._timer !== undefined) {
-                               clearInterval(this._timer);
-                               this._timer = undefined;
-                       }
-               },
-               
-               /**
-                * Changes the `delta` of the timer and `restart`s it.
-                * 
-                * @param       {int}   delta   New delta of the timer.
-                */
-               setDelta: function(delta) {
-                       this._delta = delta;
-                       
-                       this.restart();
-               }
-       };
-       
-       return Repeating;
-});
-
-/**
- * Transforms <time> elements to display the elapsed time relative to the current time.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Date/Time/Relative
- */
-define('WoltLabSuite/Core/Date/Time/Relative',['Dom/ChangeListener', 'Language', 'WoltLabSuite/Core/Date/Util', 'WoltLabSuite/Core/Timer/Repeating'], function(DomChangeListener, Language, DateUtil, Repeating) {
-       "use strict";
-       
-       var _elements = elByTag('time');
-       var _isActive = true;
-       var _isPending = false;
-       var _offset = null;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Date/Time/Relative
-        */
-       return {
-               /**
-                * Transforms <time> elements on init and binds event listeners.
-                */
-               setup: function() {
-                       new Repeating(this._refresh.bind(this), 60000);
-                       
-                       DomChangeListener.add('WoltLabSuite/Core/Date/Time/Relative', this._refresh.bind(this));
-                       
-                       document.addEventListener('visibilitychange', this._onVisibilityChange.bind(this));
-               },
-               
-               _onVisibilityChange: function () {
-                       if (document.hidden) {
-                               _isActive = false;
-                               _isPending = false;
-                       }
-                       else {
-                               _isActive = true;
-                               
-                               // force immediate refresh
-                               if (_isPending) {
-                                       this._refresh();
-                                       _isPending = false;
-                               }
-                       }
-               },
-               
-               _refresh: function() {
-                       // activity is suspended while the tab is hidden, but force an
-                       // immediate refresh once the page is active again
-                       if (!_isActive) {
-                               if (!_isPending) _isPending = true;
-                               return;
-                       }
-                       
-                       var date = new Date();
-                       var timestamp = (date.getTime() - date.getMilliseconds()) / 1000;
-                       if (_offset === null) _offset = timestamp - window.TIME_NOW;
-                       
-                       for (var i = 0, length = _elements.length; i < length; i++) {
-                               var element = _elements[i];
-                               
-                               if (!element.classList.contains('datetime') || elData(element, 'is-future-date')) continue;
-                               
-                               var elTimestamp = ~~elData(element, 'timestamp') + _offset;
-                               var elDate = elData(element, 'date');
-                               var elTime = elData(element, 'time');
-                               var elOffset = elData(element, 'offset');
-                               
-                               if (!elAttr(element, 'title')) {
-                                       elAttr(element, 'title', Language.get('wcf.date.dateTimeFormat').replace(/%date%/, elDate).replace(/%time%/, elTime));
-                               }
-                               
-                               // timestamp is less than 60 seconds ago
-                               if (elTimestamp >= timestamp || timestamp < (elTimestamp + 60)) {
-                                       element.textContent = Language.get('wcf.date.relative.now');
-                               }
-                               // timestamp is less than 60 minutes ago (display 1 hour ago rather than 60 minutes ago)
-                               else if (timestamp < (elTimestamp + 3540)) {
-                                       var minutes = Math.max(Math.round((timestamp - elTimestamp) / 60), 1);
-                                       element.textContent = Language.get('wcf.date.relative.minutes', { minutes: minutes });
-                               }
-                               // timestamp is less than 24 hours ago
-                               else if (timestamp < (elTimestamp + 86400)) {
-                                       var hours = Math.round((timestamp - elTimestamp) / 3600);
-                                       element.textContent = Language.get('wcf.date.relative.hours', { hours: hours });
-                               }
-                               // timestamp is less than 6 days ago
-                               else if (timestamp < (elTimestamp + 518400)) {
-                                       var midnight = new Date(date.getFullYear(), date.getMonth(), date.getDate());
-                                       var days = Math.ceil((midnight / 1000 - elTimestamp) / 86400);
-                                       
-                                       // get day of week
-                                       var dateObj = DateUtil.getTimezoneDate((elTimestamp * 1000), elOffset * 1000);
-                                       var dow = dateObj.getDay();
-                                       var day = Language.get('__days')[dow];
-                                       
-                                       element.textContent = Language.get('wcf.date.relative.pastDays', { days: days, day: day, time: elTime });
-                               }
-                               // timestamp is between ~700 million years BC and last week
-                               else {
-                                       element.textContent = Language.get('wcf.date.shortDateTimeFormat').replace(/%date%/, elDate).replace(/%time%/, elTime);
-                               }
-                       }
-               }
-       };
-});
-
-/**
- * Provides a touch-friendly fullscreen menu.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Page/Menu/Abstract
- */
-define('WoltLabSuite/Core/Ui/Page/Menu/Abstract',['Core', 'Environment', 'EventHandler', 'Language', 'ObjectMap', 'Dom/Traverse', 'Dom/Util', 'Ui/Screen'], function(Core, Environment, EventHandler, Language, ObjectMap, DomTraverse, DomUtil, UiScreen) {
-       "use strict";
-       
-       var _pageContainer = elById('pageContainer');
-
-       /**
-        * Which edge of the menu is touched? Empty string
-        * if no menu is currently touched.
-        * 
-        * One 'left', 'right' or ''.
-        */
-       var _androidTouching = '';
-       
-       /**
-        * @param       {string}        eventIdentifier         event namespace
-        * @param       {string}        elementId               menu element id
-        * @param       {string}        buttonSelector          CSS selector for toggle button
-        * @constructor
-        */
-       function UiPageMenuAbstract(eventIdentifier, elementId, buttonSelector) { this.init(eventIdentifier, elementId, buttonSelector); }
-       UiPageMenuAbstract.prototype = {
-               /**
-                * Initializes a touch-friendly fullscreen menu.
-                * 
-                * @param       {string}        eventIdentifier         event namespace
-                * @param       {string}        elementId               menu element id
-                * @param       {string}        buttonSelector          CSS selector for toggle button
-                */
-               init: function(eventIdentifier, elementId, buttonSelector) {
-                       if (elData(document.body, 'template') === 'packageInstallationSetup') {
-                               // work-around for WCFSetup on mobile
-                               return;
-                       }
-                       
-                       this._activeList = [];
-                       this._depth = 0;
-                       this._enabled = true;
-                       this._eventIdentifier = eventIdentifier;
-                       this._items = new ObjectMap();
-                       this._menu = elById(elementId);
-                       this._removeActiveList = false;
-                       
-                       var callbackOpen = this.open.bind(this);
-                       this._button = elBySel(buttonSelector);
-                       this._button.addEventListener(WCF_CLICK_EVENT, callbackOpen);
-                       
-                       this._initItems();
-                       this._initHeader();
-                       
-                       EventHandler.add(this._eventIdentifier, 'open', callbackOpen);
-                       EventHandler.add(this._eventIdentifier, 'close', this.close.bind(this));
-                       EventHandler.add(this._eventIdentifier, 'updateButtonState', this._updateButtonState.bind(this));
-                       
-                       var itemList, itemLists = elByClass('menuOverlayItemList', this._menu);
-                       this._menu.addEventListener('animationend', (function() {
-                               if (!this._menu.classList.contains('open')) {
-                                       for (var i = 0, length = itemLists.length; i < length; i++) {
-                                               itemList = itemLists[i];
-                                               
-                                               // force the main list to be displayed
-                                               itemList.classList.remove('active');
-                                               itemList.classList.remove('hidden');
-                                       }
-                               }
-                       }).bind(this));
-                       
-                       this._menu.children[0].addEventListener('transitionend', (function() {
-                               this._menu.classList.add('allowScroll');
-                               
-                               if (this._removeActiveList) {
-                                       this._removeActiveList = false;
-                                       
-                                       var list = this._activeList.pop();
-                                       if (list) {
-                                               list.classList.remove('activeList');
-                                       }
-                               }
-                       }).bind(this));
-                       
-                       var backdrop = elCreate('div');
-                       backdrop.className = 'menuOverlayMobileBackdrop';
-                       backdrop.addEventListener(WCF_CLICK_EVENT, this.close.bind(this));
-                       
-                       DomUtil.insertAfter(backdrop, this._menu);
-                       
-                       this._updateButtonState();
-                       
-                       if (Environment.platform() === 'android') {
-                               this._initializeAndroid();
-                       }
-               },
-               
-               /**
-                * Opens the menu.
-                * 
-                * @param       {Event}         event   event object
-                * @return      {boolean}       true if menu has been opened
-                */
-               open: function(event) {
-                       if (!this._enabled) {
-                               return false;
-                       }
-                       
-                       if (event instanceof Event) {
-                               event.preventDefault();
-                       }
-                       
-                       this._menu.classList.add('open');
-                       this._menu.classList.add('allowScroll');
-                       this._menu.children[0].classList.add('activeList');
-                       
-                       UiScreen.scrollDisable();
-                       
-                       _pageContainer.classList.add('menuOverlay-' + this._menu.id);
-                       
-                       UiScreen.pageOverlayOpen();
-                       
-                       return true;
-               },
-               
-               /**
-                * Closes the menu.
-                * 
-                * @param       {(Event|boolean)}       event   event object or boolean true to force close the menu
-                * @return      {boolean}               true if menu was open
-                */
-               close: function(event) {
-                       if (event instanceof Event) {
-                               event.preventDefault();
-                       }
-                       
-                       if (this._menu.classList.contains('open')) {
-                               this._menu.classList.remove('open');
-                               
-                               UiScreen.scrollEnable();
-                               UiScreen.pageOverlayClose();
-                               
-                               _pageContainer.classList.remove('menuOverlay-' + this._menu.id);
-                               
-                               return true;
-                       }
-                       
-                       return false;
-               },
-               
-               /**
-                * Enables the touch menu.
-                */
-               enable: function() {
-                       this._enabled = true;
-               },
-               
-               /**
-                * Disables the touch menu.
-                */
-               disable: function() {
-                       this._enabled = false;
-                       
-                       this.close(true);
-               },
-               
-               /**
-                * Initializes the Android Touch Menu.
-                */
-               _initializeAndroid: function() {
-                       var appearsAt, backdrop, touchStart;
-                       /** @const */ var AT_EDGE = 20;
-                       /** @const */ var MOVED_HORIZONTALLY = 5;
-                       /** @const */ var MOVED_VERTICALLY = 20;
-                       
-                       // specify on which side of the page the menu appears
-                       switch (this._menu.id) {
-                               case 'pageUserMenuMobile':
-                                       appearsAt = 'right';
-                               break;
-                               case 'pageMainMenuMobile':
-                                       appearsAt = 'left';
-                               break;
-                               default:
-                                       return;
-                       }
-                       
-                       backdrop = this._menu.nextElementSibling;
-                       
-                       // horizontal position of the touch start
-                       touchStart = null;
-                       
-                       document.addEventListener('touchstart', (function(event) {
-                               var touches, isOpen, isLeftEdge, isRightEdge;
-                               touches = event.touches;
-                               
-                               isOpen = this._menu.classList.contains('open');
-                               
-                               // check whether we touch the edges of the menu
-                               if (appearsAt === 'left') {
-                                       isLeftEdge = !isOpen && (touches[0].clientX < AT_EDGE);
-                                       isRightEdge = isOpen && (Math.abs(this._menu.offsetWidth - touches[0].clientX) < AT_EDGE);
-                               }
-                               else if (appearsAt === 'right') {
-                                       isLeftEdge = isOpen && (Math.abs(document.body.clientWidth - this._menu.offsetWidth - touches[0].clientX) < AT_EDGE);
-                                       isRightEdge = !isOpen && ((document.body.clientWidth - touches[0].clientX) < AT_EDGE);
-                               }
-                               
-                               // abort if more than one touch
-                               if (touches.length > 1) {
-                                       if (_androidTouching) {
-                                               Core.triggerEvent(document, 'touchend');
-                                       }
-                                       return;
-                               }
-                               
-                               // break if a touch is in progress
-                               if (_androidTouching) return;
-                               // break if no edge has been touched
-                               if (!isLeftEdge && !isRightEdge) return;
-                               // break if a different menu is open
-                               if (UiScreen.pageOverlayIsActive()) {
-                                       var found = false;
-                                       for (var i = 0; i < _pageContainer.classList.length; i++) {
-                                               if (_pageContainer.classList[i] === 'menuOverlay-' + this._menu.id) {
-                                                       found = true;
-                                               }
-                                       }
-                                       if (!found) return;
-                               }
-                               // break if redactor is in use
-                               if (document.documentElement.classList.contains('redactorActive')) return;
-                               
-                               touchStart = {
-                                       x: touches[0].clientX,
-                                       y: touches[0].clientY
-                               };
-                               
-                               if (isLeftEdge) _androidTouching = 'left';
-                               if (isRightEdge) _androidTouching = 'right';
-                       }).bind(this));
-                       
-                       document.addEventListener('touchend', (function(event) {
-                               // break if we did not start a touch
-                               if (!_androidTouching || touchStart === null) return;
-                               
-                               // break if the menu did not even start opening
-                               if (!this._menu.classList.contains('open')) {
-                                       // reset
-                                       touchStart = null;
-                                       _androidTouching = '';
-                                       return;
-                               }
-                               
-                               // last known position of the finger
-                               var position;
-                               if (event) {
-                                       position = event.changedTouches[0].clientX;
-                               }
-                               else {
-                                       position = touchStart.x;
-                               }
-                               
-                               // clean up touch styles
-                               this._menu.classList.add('androidMenuTouchEnd');
-                               this._menu.style.removeProperty('transform');
-                               backdrop.style.removeProperty(appearsAt);
-                               this._menu.addEventListener('transitionend', (function() {
-                                       this._menu.classList.remove('androidMenuTouchEnd');
-                               }).bind(this), { once: true });
-                               
-                               // check whether the user moved the finger far enough
-                               if (appearsAt === 'left') {
-                                       if (_androidTouching === 'left' && position < (touchStart.x + 100)) this.close();
-                                       if (_androidTouching === 'right' && position < (touchStart.x - 100)) this.close();
-                               }
-                               else if (appearsAt === 'right') {
-                                       if (_androidTouching === 'left' && position > (touchStart.x + 100)) this.close();
-                                       if (_androidTouching === 'right' && position > (touchStart.x - 100)) this.close();
-                               }
-                               
-                               // reset
-                               touchStart = null;
-                               _androidTouching = '';
-                       }).bind(this));
-                       
-                       document.addEventListener('touchmove', (function(event) {
-                               // break if we did not start a touch
-                               if (!_androidTouching || touchStart === null) return;
-                               
-                               var touches = event.touches;
-                               
-                               // check whether the user started moving in the correct direction
-                               // this avoids false positives, in case the user just wanted to tap
-                               var movedFromEdge = false, movedVertically = false;
-                               if (_androidTouching === 'left') movedFromEdge = touches[0].clientX > (touchStart.x + MOVED_HORIZONTALLY);
-                               if (_androidTouching === 'right') movedFromEdge = touches[0].clientX < (touchStart.x - MOVED_HORIZONTALLY);
-                               movedVertically = Math.abs(touches[0].clientY - touchStart.y) > MOVED_VERTICALLY;
-                               
-                               var isOpen = this._menu.classList.contains('open');
-                               
-                               if (!isOpen && movedFromEdge && !movedVertically) {
-                                       // the menu is not yet open, but the user moved into the right direction
-                                       this.open();
-                                       isOpen = true;
-                               }
-                               
-                               if (isOpen) {
-                                       // update CSS to the new finger position
-                                       var position = touches[0].clientX;
-                                       if (appearsAt === 'right') position = document.body.clientWidth - position;
-                                       if (position > this._menu.offsetWidth) position = this._menu.offsetWidth;
-                                       if (position < 0) position = 0;
-                                       this._menu.style.setProperty('transform', 'translateX(' + (appearsAt === 'left' ? 1 : -1) * (position - this._menu.offsetWidth) + 'px)');
-                                       backdrop.style.setProperty(appearsAt, Math.min(this._menu.offsetWidth, position) + 'px');
-                               }
-                       }).bind(this));
-               },
-               
-               /**
-                * Initializes all menu items.
-                * 
-                * @protected
-                */
-               _initItems: function() {
-                       elBySelAll('.menuOverlayItemLink', this._menu, this._initItem.bind(this));
-               },
-               
-               /**
-                * Initializes a single menu item.
-                * 
-                * @param       {Element}       item    menu item
-                * @protected
-                */
-               _initItem: function(item) {
-                       // check if it should contain a 'more' link w/ an external callback
-                       var parent = item.parentNode;
-                       var more = elData(parent, 'more');
-                       if (more) {
-                               item.addEventListener(WCF_CLICK_EVENT, (function(event) {
-                                       event.preventDefault();
-                                       event.stopPropagation();
-                                       
-                                       EventHandler.fire(this._eventIdentifier, 'more', {
-                                               handler: this,
-                                               identifier: more,
-                                               item: item,
-                                               parent: parent
-                                       });
-                               }).bind(this));
-                               
-                               return;
-                       }
-                       
-                       var itemList = item.nextElementSibling, wrapper;
-                       if (itemList === null) {
-                               return;
-                       }
-                       
-                       // handle static items with an icon-type button next to it (acp menu)
-                       if (itemList.nodeName !== 'OL' && itemList.classList.contains('menuOverlayItemLinkIcon')) {
-                               // add wrapper
-                               wrapper = elCreate('span');
-                               wrapper.className = 'menuOverlayItemWrapper';
-                               parent.insertBefore(wrapper, item);
-                               wrapper.appendChild(item);
-                               
-                               while (wrapper.nextElementSibling) {
-                                       wrapper.appendChild(wrapper.nextElementSibling);
-                               }
-                               
-                               return;
-                       }
-                       
-                       var isLink = (elAttr(item, 'href') !== '#');
-                       var parentItemList = parent.parentNode;
-                       var itemTitle = elData(itemList, 'title');
-                       
-                       this._items.set(item, {
-                               itemList: itemList,
-                               parentItemList: parentItemList
-                       });
-                       
-                       if (itemTitle === '') {
-                               itemTitle = DomTraverse.childByClass(item, 'menuOverlayItemTitle').textContent;
-                               elData(itemList, 'title', itemTitle);
-                       }
-                       
-                       var callbackLink = this._showItemList.bind(this, item);
-                       if (isLink) {
-                               wrapper = elCreate('span');
-                               wrapper.className = 'menuOverlayItemWrapper';
-                               parent.insertBefore(wrapper, item);
-                               wrapper.appendChild(item);
-                               
-                               var moreLink = elCreate('a');
-                               elAttr(moreLink, 'href', '#');
-                               moreLink.className = 'menuOverlayItemLinkIcon' + (item.classList.contains('active') ? ' active' : '');
-                               moreLink.innerHTML = '<span class="icon icon24 fa-angle-right"></span>';
-                               moreLink.addEventListener(WCF_CLICK_EVENT, callbackLink);
-                               wrapper.appendChild(moreLink);
-                       }
-                       else {
-                               item.classList.add('menuOverlayItemLinkMore');
-                               item.addEventListener(WCF_CLICK_EVENT, callbackLink);
-                       }
-                       
-                       var backLinkItem = elCreate('li');
-                       backLinkItem.className = 'menuOverlayHeader';
-                       
-                       wrapper = elCreate('span');
-                       wrapper.className = 'menuOverlayItemWrapper';
-                       
-                       var backLink = elCreate('a');
-                       elAttr(backLink, 'href', '#');
-                       backLink.className = 'menuOverlayItemLink menuOverlayBackLink';
-                       backLink.textContent = elData(parentItemList, 'title');
-                       backLink.addEventListener(WCF_CLICK_EVENT, this._hideItemList.bind(this, item));
-                       
-                       var closeLink = elCreate('a');
-                       elAttr(closeLink, 'href', '#');
-                       closeLink.className = 'menuOverlayItemLinkIcon';
-                       closeLink.innerHTML = '<span class="icon icon24 fa-times"></span>';
-                       closeLink.addEventListener(WCF_CLICK_EVENT, this.close.bind(this));
-                       
-                       wrapper.appendChild(backLink);
-                       wrapper.appendChild(closeLink);
-                       backLinkItem.appendChild(wrapper);
-                       
-                       itemList.insertBefore(backLinkItem, itemList.firstElementChild);
-                       
-                       if (!backLinkItem.nextElementSibling.classList.contains('menuOverlayTitle')) {
-                               var titleItem = elCreate('li');
-                               titleItem.className = 'menuOverlayTitle';
-                               var title = elCreate('span');
-                               title.textContent = itemTitle;
-                               titleItem.appendChild(title);
-                               
-                               itemList.insertBefore(titleItem, backLinkItem.nextElementSibling);
-                       }
-               },
-               
-               /**
-                * Renders the menu item list header.
-                * 
-                * @protected
-                */
-               _initHeader: function() {
-                       var listItem = elCreate('li');
-                       listItem.className = 'menuOverlayHeader';
-                       
-                       var wrapper = elCreate('span');
-                       wrapper.className = 'menuOverlayItemWrapper';
-                       listItem.appendChild(wrapper);
-                       
-                       var logoWrapper = elCreate('span');
-                       logoWrapper.className = 'menuOverlayLogoWrapper';
-                       wrapper.appendChild(logoWrapper);
-                       
-                       var logo = elCreate('span');
-                       logo.className = 'menuOverlayLogo';
-                       logo.style.setProperty('background-image', 'url("' + elData(this._menu, 'page-logo') + '")', '');
-                       logoWrapper.appendChild(logo);
-                       
-                       var closeLink = elCreate('a');
-                       elAttr(closeLink, 'href', '#');
-                       closeLink.className = 'menuOverlayItemLinkIcon';
-                       closeLink.innerHTML = '<span class="icon icon24 fa-times"></span>';
-                       closeLink.addEventListener(WCF_CLICK_EVENT, this.close.bind(this));
-                       wrapper.appendChild(closeLink);
-                       
-                       var list = DomTraverse.childByClass(this._menu, 'menuOverlayItemList');
-                       list.insertBefore(listItem, list.firstElementChild);
-               },
-               
-               /**
-                * Hides an item list, return to the parent item list.
-                * 
-                * @param       {Element}       item    menu item
-                * @param       {Event}         event   event object
-                * @protected
-                */
-               _hideItemList: function(item, event) {
-                       if (event instanceof Event) {
-                               event.preventDefault();
-                       }
-                       
-                       this._menu.classList.remove('allowScroll');
-                       this._removeActiveList = true;
-                       
-                       var data = this._items.get(item);
-                       data.parentItemList.classList.remove('hidden');
-                       
-                       this._updateDepth(false);
-               },
-               
-               /**
-                * Shows the child item list.
-                * 
-                * @param       {Element}       item    menu item
-                * @param event
-                * @private
-                */
-               _showItemList: function(item, event) {
-                       if (event instanceof Event) {
-                               event.preventDefault();
-                       }
-                       
-                       var data = this._items.get(item);
-                       
-                       var load = elData(data.itemList, 'load');
-                       if (load) {
-                               if (!elDataBool(item, 'loaded')) {
-                                       var icon = event.currentTarget.firstElementChild;
-                                       if (icon.classList.contains('fa-angle-right')) {
-                                               icon.classList.remove('fa-angle-right');
-                                               icon.classList.add('fa-spinner');
-                                       }
-                                       
-                                       EventHandler.fire(this._eventIdentifier, 'load_' + load);
-                                       
-                                       return;
-                               }
-                       }
-                       
-                       this._menu.classList.remove('allowScroll');
-                       
-                       data.itemList.classList.add('activeList');
-                       data.parentItemList.classList.add('hidden');
-                       
-                       this._activeList.push(data.itemList);
-                       
-                       this._updateDepth(true);
-               },
-               
-               _updateDepth: function(increase) {
-                       this._depth += (increase) ? 1 : -1;
-                       
-                       var offset = this._depth * -100;
-                       if (Language.get('wcf.global.pageDirection') === 'rtl') {
-                               // reverse logic for RTL
-                               offset *= -1;
-                       }
-                       
-                       this._menu.children[0].style.setProperty('transform', 'translateX(' + offset + '%)', '');
-               },
-               
-               _updateButtonState: function() {
-                       var hasNewContent = false;
-                       elBySelAll('.badgeUpdate', this._menu, function (badge) {
-                               if (~~badge.textContent > 0) {
-                                       hasNewContent = true;
-                               }
-                       });
-                       
-                       this._button.classList[(hasNewContent ? 'add' : 'remove')]('pageMenuMobileButtonHasContent');
-               }
-       };
-       
-       return UiPageMenuAbstract;
-});
-
-/**
- * Provides the touch-friendly fullscreen main menu.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Page/Menu/Main
- */
-define('WoltLabSuite/Core/Ui/Page/Menu/Main',['Core', 'Language', 'Dom/Traverse', './Abstract'], function(Core, Language, DomTraverse, UiPageMenuAbstract) {
-       "use strict";
-       
-       var _optionsTitle = null, _hasItems = null, _list = null, _navigationList = null, _callbackClose = null;
-       
-       /**
-        * @constructor
-        */
-       function UiPageMenuMain() { this.init(); }
-       Core.inherit(UiPageMenuMain, UiPageMenuAbstract, {
-               /**
-                * Initializes the touch-friendly fullscreen main menu.
-                */
-               init: function() {
-                       UiPageMenuMain._super.prototype.init.call(
-                               this,
-                               'com.woltlab.wcf.MainMenuMobile',
-                               'pageMainMenuMobile',
-                               '#pageHeader .mainMenu'
-                       );
-                       
-                       _optionsTitle = elById('pageMainMenuMobilePageOptionsTitle');
-                       if (_optionsTitle !== null) {
-                               _list = DomTraverse.childByClass(_optionsTitle, 'menuOverlayItemList');
-                               _navigationList = elBySel('.jsPageNavigationIcons');
-                               
-                               _callbackClose = (function(event) {
-                                       this.close();
-                                       event.stopPropagation();
-                               }).bind(this);
-                       }
-                       
-                       elAttr(this._button, 'aria-label', Language.get('wcf.menu.page'));
-                       elAttr(this._button, 'role', 'button');
-               },
-               
-               open: function (event) {
-                       if (!UiPageMenuMain._super.prototype.open.call(this, event)) {
-                               return false;
-                       }
-                       
-                       if (_optionsTitle === null) {
-                               return true;
-                       }
-                       
-                       _hasItems = _navigationList && _navigationList.childElementCount > 0;
-                       
-                       if (_hasItems) {
-                               var item, link;
-                               while (_navigationList.childElementCount) {
-                                       item = _navigationList.children[0];
-                                       
-                                       item.classList.add('menuOverlayItem');
-                                       item.classList.add('menuOverlayItemOption');
-                                       item.addEventListener(WCF_CLICK_EVENT, _callbackClose);
-                                       
-                                       link = item.children[0];
-                                       link.classList.add('menuOverlayItemLink');
-                                       link.classList.add('box24');
-                                       
-                                       link.children[1].classList.remove('invisible');
-                                       link.children[1].classList.add('menuOverlayItemTitle');
-                                       
-                                       _optionsTitle.parentNode.insertBefore(item, _optionsTitle.nextSibling);
-                               }
-                               
-                               elShow(_optionsTitle);
-                       }
-                       else {
-                               elHide(_optionsTitle);
-                       }
-                       
-                       return true;
-               },
-               
-               close: function(event) {
-                       if (!UiPageMenuMain._super.prototype.close.call(this, event)) {
-                               return false;
-                       }
-                       
-                       if (_hasItems) {
-                               elHide(_optionsTitle);
-                               
-                               var item = _optionsTitle.nextElementSibling;
-                               var link;
-                               while (item && item.classList.contains('menuOverlayItemOption')) {
-                                       item.classList.remove('menuOverlayItem');
-                                       item.classList.remove('menuOverlayItemOption');
-                                       item.removeEventListener(WCF_CLICK_EVENT, _callbackClose);
-                                       
-                                       link = item.children[0];
-                                       link.classList.remove('menuOverlayItemLink');
-                                       link.classList.remove('box24');
-                                       
-                                       link.children[1].classList.add('invisible');
-                                       link.children[1].classList.remove('menuOverlayItemTitle');
-                                       
-                                       _navigationList.appendChild(item);
-                                       
-                                       item = item.nextElementSibling;
-                               }
-                       }
-                       
-                       return true;
-               }
-       });
-       
-       return UiPageMenuMain;
-});
-
-/**
- * Provides the touch-friendly fullscreen user menu.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Page/Menu/User
- */
-define('WoltLabSuite/Core/Ui/Page/Menu/User',['Core', 'EventHandler', 'Language', './Abstract'], function(Core, EventHandler, Language, UiPageMenuAbstract) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function UiPageMenuUser() { this.init(); }
-       Core.inherit(UiPageMenuUser, UiPageMenuAbstract, {
-               /**
-                * Initializes the touch-friendly fullscreen user menu.
-                */
-               init: function() {
-                       // check if user menu is actually empty
-                       var menu = elBySel('#pageUserMenuMobile > .menuOverlayItemList');
-                       if (menu.childElementCount === 1 && menu.children[0].classList.contains('menuOverlayTitle')) {
-                               elBySel('#pageHeader .userPanel').classList.add('hideUserPanel');
-                               return;
-                       }
-                       
-                       UiPageMenuUser._super.prototype.init.call(
-                               this,
-                               'com.woltlab.wcf.UserMenuMobile',
-                               'pageUserMenuMobile',
-                               '#pageHeader .userPanel'
-                       );
-                       
-                       EventHandler.add('com.woltlab.wcf.userMenu', 'updateBadge', (function (data) {
-                               elBySelAll('.menuOverlayItemBadge', this._menu, (function (item) {
-                                       if (elData(item, 'badge-identifier') === data.identifier) {
-                                               var badge = elBySel('.badge', item);
-                                               if (data.count) {
-                                                       if (badge === null) {
-                                                               badge = elCreate('span');
-                                                               badge.className = 'badge badgeUpdate';
-                                                               item.appendChild(badge);
-                                                       }
-                                                       
-                                                       badge.textContent = data.count;
-                                               }
-                                               else if (badge !== null) {
-                                                       elRemove(badge);
-                                               }
-                                               
-                                               this._updateButtonState();
-                                       }
-                               }).bind(this));
-                       }).bind(this));
-                       
-                       elAttr(this._button, 'aria-label', Language.get('wcf.menu.user'));
-                       elAttr(this._button, 'role', 'button');
-               },
-               
-               close: function (event) {
-                       var dropdown = WCF.Dropdown.Interactive.Handler.getOpenDropdown();
-                       if (dropdown) {
-                               event.preventDefault();
-                               event.stopPropagation();
-                               
-                               dropdown.close();
-                       }
-                       else {
-                               UiPageMenuUser._super.prototype.close.call(this, event);
-                       }
-               }
-       });
-       
-       return UiPageMenuUser;
-});
-
-/**
- * Simple interface to work with reusable dropdowns that are not bound to a specific item.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Dropdown/Reusable
- */
-define('WoltLabSuite/Core/Ui/Dropdown/Reusable',['Dictionary', 'Ui/SimpleDropdown'], function(Dictionary, UiSimpleDropdown) {
-       "use strict";
-       
-       var _dropdowns = new Dictionary();
-       var _ghostElementId = 0;
-       
-       /**
-        * Returns dropdown name by internal identifier.
-        *
-        * @param       {string}        identifier      internal identifier
-        * @returns     {string}        dropdown name
-        */
-       function _getDropdownName(identifier) {
-               if (!_dropdowns.has(identifier)) {
-                       throw new Error("Unknown dropdown identifier '" + identifier + "'");
-               }
-               
-               return _dropdowns.get(identifier);
-       }
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Dropdown/Reusable
-        */
-       return {
-               /**
-                * Initializes a new reusable dropdown.
-                * 
-                * @param       {string}        identifier      internal identifier
-                * @param       {Element}       menu            dropdown menu element
-                */
-               init: function(identifier, menu) {
-                       if (_dropdowns.has(identifier)) {
-                               return;
-                       }
-                       
-                       var ghostElement = elCreate('div');
-                       ghostElement.id = 'reusableDropdownGhost' + _ghostElementId++;
-                       
-                       UiSimpleDropdown.initFragment(ghostElement, menu);
-                       
-                       _dropdowns.set(identifier, ghostElement.id);
-               },
-               
-               /**
-                * Returns the dropdown menu element.
-                * 
-                * @param       {string}        identifier      internal identifier
-                * @returns     {Element}       dropdown menu element
-                */
-               getDropdownMenu: function(identifier) {
-                       return UiSimpleDropdown.getDropdownMenu(_getDropdownName(identifier));
-               },
-               
-               /**
-                * Registers a callback invoked upon open and close.
-                * 
-                * @param       {string}        identifier      internal identifier
-                * @param       {function}      callback        callback function
-                */
-               registerCallback: function(identifier, callback) {
-                       UiSimpleDropdown.registerCallback(_getDropdownName(identifier), callback);
-               },
-               
-               /**
-                * Toggles a dropdown.
-                * 
-                * @param       {string}        identifier              internal identifier
-                * @param       {Element}       referenceElement        reference element used for alignment
-                */
-               toggleDropdown: function(identifier, referenceElement) {
-                       UiSimpleDropdown.toggleDropdown(_getDropdownName(identifier), referenceElement);
-               }
-       };
-});
-
-/**
- * Modifies the interface to provide a better usability for mobile devices.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Mobile
- */
-define(
-       'WoltLabSuite/Core/Ui/Mobile',[        'Core', 'Environment', 'EventHandler', 'Language', 'List', 'Dom/ChangeListener', 'Dom/Traverse', 'Ui/Alignment', 'Ui/CloseOverlay', 'Ui/Screen', './Page/Menu/Main', './Page/Menu/User', 'WoltLabSuite/Core/Ui/Dropdown/Reusable'],
-       function(Core,    Environment,   EventHandler,   Language,   List,   DomChangeListener,    DomTraverse,    UiAlignment, UiCloseOverlay,    UiScreen,    UiPageMenuMain,     UiPageMenuUser, UiDropdownReusable)
-{
-       "use strict";
-       
-       var _buttonGroupNavigations = elByClass('buttonGroupNavigation');
-       var _callbackCloseDropdown = null;
-       var _dropdownMenu = null;
-       var _dropdownMenuMessage = null;
-       var _enabled = false;
-       var _knownMessages = new List();
-       var _main = null;
-       var _messages = elByClass('message');
-       var _options = {};
-       var _pageMenuMain = null;
-       var _pageMenuUser = null;
-       var _messageGroups = null;
-       var _sidebars = [];
-       var _sidebarXsEnabled = false;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Mobile
-        */
-       return {
-               /**
-                * Initializes the mobile UI.
-                * 
-                * @param       {Object=}       options         initialization options
-                */
-               setup: function(options) {
-                       _options = Core.extend({
-                               enableMobileMenu: true
-                       }, options);
-                       
-                       _main = elById('main');
-                       
-                       elBySelAll('.sidebar', undefined, function (sidebar) {
-                               _sidebars.push(sidebar);
-                       });
-                       
-                       if (Environment.touch()) {
-                               document.documentElement.classList.add('touch');
-                       }
-                       
-                       if (Environment.platform() !== 'desktop') {
-                               document.documentElement.classList.add('mobile');
-                       }
-                       
-                       var messageGroupList = elBySel('.messageGroupList');
-                       if (messageGroupList) _messageGroups = elByClass('messageGroup', messageGroupList);
-                       
-                       UiScreen.on('screen-md-down', {
-                               match: this.enable.bind(this),
-                               unmatch: this.disable.bind(this),
-                               setup: this._init.bind(this)
-                       });
-                       
-                       UiScreen.on('screen-sm-down', {
-                               match: this.enableShadow.bind(this),
-                               unmatch: this.disableShadow.bind(this),
-                               setup: this.enableShadow.bind(this)
-                       });
-                       
-                       UiScreen.on('screen-xs', {
-                               match: this._enableSidebarXS.bind(this),
-                               unmatch: this._disableSidebarXS.bind(this),
-                               setup: this._setupSidebarXS.bind(this)
-                       });
-               },
-               
-               /**
-                * Enables the mobile UI.
-                */
-               enable: function() {
-                       _enabled = true;
-                       
-                       if (_options.enableMobileMenu) {
-                               _pageMenuMain.enable();
-                               _pageMenuUser.enable();
-                       }
-               },
-               
-               /**
-                * Enables shadow links for larger click areas on messages. 
-                */
-               enableShadow: function () {
-                       if (_messageGroups) this.rebuildShadow(_messageGroups, '.messageGroupLink');
-               },
-               
-               /**
-                * Disables the mobile UI.
-                */
-               disable: function() {
-                       _enabled = false;
-                       
-                       if (_options.enableMobileMenu) {
-                               _pageMenuMain.disable();
-                               _pageMenuUser.disable();
-                       }
-               },
-               
-               /**
-                * Disables shadow links.
-                */
-               disableShadow: function () {
-                       if (_messageGroups) this.removeShadow(_messageGroups);
-                       
-                       if (_dropdownMenu) _callbackCloseDropdown();
-               },
-               
-               _init: function() {
-                       _enabled = true;
-                       
-                       this._initSearchBar();
-                       this._initButtonGroupNavigation();
-                       this._initMessages();
-                       this._initMobileMenu();
-                       
-                       UiCloseOverlay.add('WoltLabSuite/Core/Ui/Mobile', this._closeAllMenus.bind(this));
-                       DomChangeListener.add('WoltLabSuite/Core/Ui/Mobile', (function() {
-                               this._initButtonGroupNavigation();
-                               this._initMessages();
-                       }).bind(this));
-               },
-               
-               _initSearchBar: function() {
-                       var _searchBar = elById('pageHeaderSearch');
-                       var _searchInput = elById('pageHeaderSearchInput');
-                       
-                       var scrollTop = null;
-                       
-                       EventHandler.add('com.woltlab.wcf.MainMenuMobile', 'more', function(data) {
-                               if (data.identifier === 'com.woltlab.wcf.search') {
-                                       data.handler.close(true);
-                                       
-                                       if (Environment.platform() === 'ios') {
-                                               scrollTop = document.body.scrollTop;
-                                               UiScreen.scrollDisable();
-                                       }
-                                       
-                                       _searchBar.style.setProperty('top', elById('pageHeader').offsetHeight + 'px', '');
-                                       _searchBar.classList.add('open');
-                                       _searchInput.focus();
-                                       
-                                       if (Environment.platform() === 'ios') {
-                                               document.body.scrollTop = 0;
-                                       }
-                               }
-                       });
-                       
-                       _main.addEventListener(WCF_CLICK_EVENT, function() {
-                               if (_searchBar) _searchBar.classList.remove('open');
-                               
-                               if (Environment.platform() === 'ios' && scrollTop !== null) {
-                                       UiScreen.scrollEnable();
-                                       document.body.scrollTop = scrollTop; 
-                                       
-                                       scrollTop = null;
-                               }
-                       });
-               },
-               
-               _initButtonGroupNavigation: function() {
-                       for (var i = 0, length = _buttonGroupNavigations.length; i < length; i++) {
-                               var navigation = _buttonGroupNavigations[i];
-                               
-                               if (navigation.classList.contains('jsMobileButtonGroupNavigation')) continue;
-                               else navigation.classList.add('jsMobileButtonGroupNavigation');
-                               
-                               var list = elBySel('.buttonList', navigation);
-                               if (list.childElementCount === 0) {
-                                       // ignore objects without options
-                                       continue;
-                               }
-                               
-                               navigation.parentNode.classList.add('hasMobileNavigation');
-                               
-                               var button = elCreate('a');
-                               button.className = 'dropdownLabel';
-                               
-                               var span = elCreate('span');
-                               span.className = 'icon icon24 fa-ellipsis-v';
-                               button.appendChild(span);
-                               
-                               (function(navigation, button, list) {
-                                       button.addEventListener(WCF_CLICK_EVENT, function(event) {
-                                               event.preventDefault();
-                                               event.stopPropagation();
-                                               
-                                               navigation.classList.toggle('open');
-                                       });
-                                       
-                                       list.addEventListener(WCF_CLICK_EVENT, function(event) {
-                                               event.stopPropagation();
-                                               
-                                               navigation.classList.remove('open');
-                                       });
-                               })(navigation, button, list);
-                               
-                               navigation.insertBefore(button, navigation.firstChild);
-                       }
-               },
-               
-               _initMessages: function() {
-                       Array.prototype.forEach.call(_messages, (function(message) {
-                               if (_knownMessages.has(message)) {
-                                       return;
-                               }
-                               
-                               var navigation = elBySel('.jsMobileNavigation', message);
-                               if (navigation) {
-                                       navigation.addEventListener(WCF_CLICK_EVENT, function(event) {
-                                               event.stopPropagation();
-                                               
-                                               // mimic dropdown behavior
-                                               window.setTimeout(function () {
-                                                       navigation.classList.remove('open');
-                                               }, 10);
-                                       });
-                                       
-                                       var quickOptions = elBySel('.messageQuickOptions', message);
-                                       if (quickOptions && navigation.childElementCount) {
-                                               quickOptions.classList.add('active');
-                                               quickOptions.addEventListener(WCF_CLICK_EVENT, (function (event) {
-                                                       if (_enabled && event.target.nodeName !== 'LABEL' && event.target.nodeName !== 'INPUT') {
-                                                               event.preventDefault();
-                                                               event.stopPropagation();
-                                                               
-                                                               this._toggleMobileNavigation(message, quickOptions, navigation);
-                                                       }
-                                               }).bind(this));
-                                       }
-                               }
-                               
-                               _knownMessages.add(message);
-                       }).bind(this));
-               },
-               
-               _initMobileMenu: function() {
-                       if (_options.enableMobileMenu) {
-                               _pageMenuMain = new UiPageMenuMain();
-                               _pageMenuUser = new UiPageMenuUser();
-                       }
-               },
-               
-               _closeAllMenus: function() {
-                       elBySelAll('.jsMobileButtonGroupNavigation.open, .jsMobileNavigation.open', null, function (menu) {
-                               menu.classList.remove('open');
-                       });
-                       
-                       if (_enabled && _dropdownMenu) _callbackCloseDropdown();
-               },
-               
-               rebuildShadow: function(elements, linkSelector) {
-                       var element, parent, shadow;
-                       for (var i = 0, length = elements.length; i < length; i++) {
-                               element = elements[i];
-                               parent = element.parentNode;
-                               
-                               shadow = DomTraverse.childByClass(parent, 'mobileLinkShadow');
-                               if (shadow === null) {
-                                       if (elBySel(linkSelector, element).href) {
-                                               shadow = elCreate('a');
-                                               shadow.className = 'mobileLinkShadow';
-                                               shadow.href = elBySel(linkSelector, element).href;
-                                               
-                                               parent.appendChild(shadow);
-                                               parent.classList.add('mobileLinkShadowContainer');
-                                       }
-                               }
-                       }
-               },
-               
-               removeShadow: function(elements) {
-                       var element, parent, shadow;
-                       for (var i = 0, length = elements.length; i < length; i++) {
-                               element = elements[i];
-                               parent = element.parentNode;
-                               
-                               if (parent.classList.contains('mobileLinkShadowContainer')) {
-                                       shadow = DomTraverse.childByClass(parent, 'mobileLinkShadow');
-                                       if (shadow !== null) {
-                                               elRemove(shadow);
-                                       }
-                                       
-                                       parent.classList.remove('mobileLinkShadowContainer');
-                               }
-                       }
-               },
-               
-               _enableSidebarXS: function() {
-                       _sidebarXsEnabled = true;
-               },
-               
-               _disableSidebarXS: function() {
-                       _sidebarXsEnabled = false;
-                       
-                       _sidebars.forEach(function (sidebar) {
-                               sidebar.classList.remove('open');
-                       });
-               },
-               
-               _setupSidebarXS: function() {
-                       _sidebars.forEach(function (sidebar) {
-                               sidebar.addEventListener('mousedown', function(event) {
-                                       if (_sidebarXsEnabled && event.target === sidebar) {
-                                               event.preventDefault();
-                                               
-                                               sidebar.classList.toggle('open');
-                                       }
-                               });
-                       });
-                       
-                       _sidebarXsEnabled = true;
-               },
-               
-               _toggleMobileNavigation: function (message, quickOptions, navigation) {
-                       if (_dropdownMenu === null) {
-                               _dropdownMenu = elCreate('ul');
-                               _dropdownMenu.className = 'dropdownMenu';
-                               
-                               UiDropdownReusable.init('com.woltlab.wcf.jsMobileNavigation', _dropdownMenu);
-                               
-                               _callbackCloseDropdown = function () {
-                                       _dropdownMenu.classList.remove('dropdownOpen');
-                               }
-                       }
-                       else if (_dropdownMenu.classList.contains('dropdownOpen')) {
-                               _callbackCloseDropdown();
-                               
-                               if (_dropdownMenuMessage === message) {
-                                       // toggle behavior
-                                       return;
-                               }
-                       }
-                       
-                       _dropdownMenu.innerHTML = '';
-                       UiCloseOverlay.execute();
-                       
-                       this._rebuildMobileNavigation(navigation);
-                       
-                       var previousNavigation = navigation.previousElementSibling;
-                       if (previousNavigation && previousNavigation.classList.contains('messageFooterButtonsExtra')) {
-                               var divider = elCreate('li');
-                               divider.className = 'dropdownDivider';
-                               _dropdownMenu.appendChild(divider);
-                               
-                               this._rebuildMobileNavigation(previousNavigation);
-                       }
-                       
-                       UiAlignment.set(_dropdownMenu, quickOptions, {
-                               horizontal: 'right',
-                               allowFlip: 'vertical'
-                       });
-                       _dropdownMenu.classList.add('dropdownOpen');
-                       
-                       _dropdownMenuMessage = message;
-               },
-               
-               _rebuildMobileNavigation: function (navigation) {
-                       elBySelAll('.button:not(.ignoreMobileNavigation)', navigation, function (button) {
-                               var item = elCreate('li');
-                               if (button.classList.contains('active')) item.className = 'active';
-                               item.innerHTML = '<a href="#">' + elBySel('span:not(.icon)', button).textContent + '</a>';
-                               item.children[0].addEventListener(WCF_CLICK_EVENT, function (event) {
-                                       event.preventDefault();
-                                       event.stopPropagation();
-                                       
-                                       if (button.nodeName === 'A') button.click();
-                                       else Core.triggerEvent(button, WCF_CLICK_EVENT);
-                                       
-                                       _callbackCloseDropdown();
-                               });
-                               
-                               _dropdownMenu.appendChild(item);
-                       });
-               }
-       };
-});
-
-/**
- * Simple tab menu implementation with a straight-forward logic.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/TabMenu/Simple
- */
-define('WoltLabSuite/Core/Ui/TabMenu/Simple',['Dictionary', 'EventHandler', 'Dom/Traverse', 'Dom/Util'], function(Dictionary, EventHandler, DomTraverse, DomUtil) {
-       "use strict";
-       
-       /**
-        * @param       {Element}       container       container element
-        * @constructor
-        */
-       function TabMenuSimple(container) {
-               this._container = container;
-               this._containers = new Dictionary();
-               this._isLegacy = null;
-               this._store = null;
-               this._tabs = new Dictionary();
-       }
-       
-       TabMenuSimple.prototype = {
-               /**
-                * Validates the properties and DOM structure of this container.
-                * 
-                * Expected DOM:
-                * <div class="tabMenuContainer">
-                *      <nav>
-                *              <ul>
-                *                      <li data-name="foo"><a>bar</a></li>
-                *              </ul>
-                *      </nav>
-                *      
-                *      <div id="foo">baz</div>
-                * </div>
-                * 
-                * @return      {boolean}       false if any properties are invalid or the DOM does not match the expectations
-                */
-               validate: function() {
-                       if (!this._container.classList.contains('tabMenuContainer')) {
-                               return false;
-                       }
-                       
-                       var nav = DomTraverse.childByTag(this._container, 'NAV');
-                       if (nav === null) {
-                               return false;
-                       }
-                       
-                       // get children
-                       var tabs = elByTag('li', nav);
-                       if (tabs.length === 0) {
-                               return false;
-                       }
-                       
-                       var container, containers = DomTraverse.childrenByTag(this._container, 'DIV'), name, i, length;
-                       for (i = 0, length = containers.length; i < length; i++) {
-                               container = containers[i];
-                               name = elData(container, 'name');
-                               
-                               if (!name) {
-                                       name = DomUtil.identify(container);
-                               }
-                               
-                               elData(container, 'name', name);
-                               this._containers.set(name, container);
-                       }
-                       
-                       var containerId = this._container.id, tab;
-                       for (i = 0, length = tabs.length; i < length; i++) {
-                               tab = tabs[i];
-                               name = this._getTabName(tab);
-                               
-                               if (!name) {
-                                       continue;
-                               }
-                               
-                               if (this._tabs.has(name)) {
-                                       throw new Error("Tab names must be unique, li[data-name='" + name + "'] (tab menu id: '" + containerId + "') exists more than once.");
-                               }
-                               
-                               container = this._containers.get(name);
-                               if (container === undefined) {
-                                       throw new Error("Expected content element for li[data-name='" + name + "'] (tab menu id: '" + containerId + "').");
-                               }
-                               else if (container.parentNode !== this._container) {
-                                       throw new Error("Expected content element '" + name + "' (tab menu id: '" + containerId + "') to be a direct children.");
-                               }
-                               
-                               // check if tab holds exactly one children which is an anchor element
-                               if (tab.childElementCount !== 1 || tab.children[0].nodeName !== 'A') {
-                                       throw new Error("Expected exactly one <a> as children for li[data-name='" + name + "'] (tab menu id: '" + containerId + "').");
-                               }
-                               
-                               this._tabs.set(name, tab);
-                       }
-                       
-                       if (!this._tabs.size) {
-                               throw new Error("Expected at least one tab (tab menu id: '" + containerId + "').");
-                       }
-                       
-                       if (this._isLegacy) {
-                               elData(this._container, 'is-legacy', true);
-                               
-                               this._tabs.forEach(function(tab, name) {
-                                       elAttr(tab, 'aria-controls', name);
-                               });
-                       }
-                       
-                       return true;
-               },
-               
-               /**
-                * Initializes this tab menu.
-                * 
-                * @param       {Dictionary=}   oldTabs         previous list of tabs
-                * @return      {?Element}      parent tab for selection or null
-                */
-               init: function(oldTabs) {
-                       oldTabs = oldTabs || null;
-                       
-                       // bind listeners
-                       this._tabs.forEach((function(tab) {
-                               if (!oldTabs || oldTabs.get(elData(tab, 'name')) !== tab) {
-                                       tab.children[0].addEventListener(WCF_CLICK_EVENT, this._onClick.bind(this));
-                               }
-                       }).bind(this));
-                       
-                       var returnValue = null;
-                       if (!oldTabs) {
-                               var hash = TabMenuSimple.getIdentifierFromHash();
-                               var selectTab = null;
-                               if (hash !== '') {
-                                       selectTab = this._tabs.get(hash);
-                                       
-                                       // check for parent tab menu
-                                       if (selectTab && this._container.parentNode.classList.contains('tabMenuContainer')) {
-                                               returnValue = this._container;
-                                       }
-                               }
-                               
-                               if (!selectTab) {
-                                       var preselect = elData(this._container, 'preselect') || elData(this._container, 'active');
-                                       if (preselect === "true" || !preselect) preselect = true;
-                                       
-                                       if (preselect === true) {
-                                               this._tabs.forEach(function(tab) {
-                                                       if (!selectTab && !elIsHidden(tab) && (!tab.previousElementSibling || elIsHidden(tab.previousElementSibling))) {
-                                                               selectTab = tab;
-                                                       }
-                                               });
-                                       }
-                                       else if (preselect !== "false") {
-                                               selectTab = this._tabs.get(preselect);
-                                       }
-                               }
-                               
-                               if (selectTab) {
-                                       this._containers.forEach(function(container) {
-                                               container.classList.add('hidden');
-                                       });
-                                       
-                                       this.select(null, selectTab, true);
-                               }
-                               
-                               var store = elData(this._container, 'store');
-                               if (store) {
-                                       var input = elCreate('input');
-                                       input.type = 'hidden';
-                                       input.name = store;
-                                       input.value = elData(this.getActiveTab(), 'name');
-                                       
-                                       this._container.appendChild(input);
-                                       
-                                       this._store = input;
-                               }
-                       }
-                       
-                       return returnValue;
-               },
-               
-               /**
-                * Selects a tab.
-                * 
-                * @param       {?(string|int)}         name            tab name or sequence no
-                * @param       {Element=}              tab             tab element
-                * @param       {boolean=}              disableEvent    suppress event handling
-                */
-               select: function(name, tab, disableEvent) {
-                       tab = tab || this._tabs.get(name);
-                       
-                       if (!tab) {
-                               // check if name is an integer
-                               if (~~name == name) {
-                                       name = ~~name;
-                                       
-                                       var i = 0;
-                                       this._tabs.forEach(function(item) {
-                                               if (i === name) {
-                                                       tab = item;
-                                               }
-                                               
-                                               i++;
-                                       });
-                               }
-                               
-                               if (!tab) {
-                                       throw new Error("Expected a valid tab name, '" + name + "' given (tab menu id: '" + this._container.id + "').");
-                               }
-                       }
-                       
-                       name = name || elData(tab, 'name');
-                       
-                       // unmark active tab
-                       var oldTab = this.getActiveTab();
-                       var oldContent = null;
-                       if (oldTab) {
-                               var oldTabName = elData(oldTab, 'name');
-                               if (oldTabName === name) {
-                                       // same tab
-                                       return;
-                               }
-                               
-                               if (!disableEvent) {
-                                       EventHandler.fire('com.woltlab.wcf.simpleTabMenu_' + this._container.id, 'beforeSelect', {
-                                               tab: oldTab,
-                                               tabName: oldTabName
-                                       });
-                               }
-                               
-                               oldTab.classList.remove('active');
-                               oldContent = this._containers.get(elData(oldTab, 'name'));
-                               oldContent.classList.remove('active');
-                               oldContent.classList.add('hidden');
-                               
-                               if (this._isLegacy) {
-                                       oldTab.classList.remove('ui-state-active');
-                                       oldContent.classList.remove('ui-state-active');
-                               }
-                       }
-                       
-                       tab.classList.add('active');
-                       var newContent = this._containers.get(name);
-                       newContent.classList.add('active');
-                       newContent.classList.remove('hidden');
-                       
-                       if (this._isLegacy) {
-                               tab.classList.add('ui-state-active');
-                               newContent.classList.add('ui-state-active');
-                       }
-                       
-                       if (this._store) {
-                               this._store.value = name;
-                       }
-                       
-                       if (!disableEvent) {
-                               EventHandler.fire('com.woltlab.wcf.simpleTabMenu_' + this._container.id, 'select', {
-                                       active: tab,
-                                       activeName: name,
-                                       previous: oldTab,
-                                       previousName: oldTab ? elData(oldTab, 'name') : null
-                               });
-                               
-                               var jQuery = (this._isLegacy && typeof window.jQuery === 'function') ? window.jQuery : null;
-                               if (jQuery) {
-                                       // simulate jQuery UI Tabs event
-                                       jQuery(this._container).trigger('wcftabsbeforeactivate', {
-                                               newTab: jQuery(tab),
-                                               oldTab: jQuery(oldTab),
-                                               newPanel: jQuery(newContent),
-                                               oldPanel: jQuery(oldContent)
-                                       });
-                               }
-                               
-                               var location = window.location.href.replace(/#+[^#]*$/, '');
-                               if (TabMenuSimple.getIdentifierFromHash() === name) {
-                                       location += window.location.hash;
-                               }
-                               else {
-                                       location += '#' + name;
-                               }
-                               
-                               // update history
-                               //noinspection JSCheckFunctionSignatures
-                               window.history.replaceState(
-                                       undefined,
-                                       undefined,
-                                       location
-                               );
-                       }
-                       
-                       require(['WoltLabSuite/Core/Ui/TabMenu'], function (UiTabMenu) {
-                               //noinspection JSUnresolvedFunction
-                               UiTabMenu.scrollToTab(tab);
-                       });
-               },
-               
-               /**
-                * Selects the first visible tab of the tab menu and return `true`. If there is no
-                * visible tab, `false` is returned.
-                * 
-                * The visibility of a tab is determined by calling `elIsHidden` with the tab menu
-                * item as the parameter.
-                *
-                * @return      {boolean}
-                */
-               selectFirstVisible: function() {
-                       var selectTab;
-                       this._tabs.forEach(function(tab) {
-                               if (!selectTab && !elIsHidden(tab)) {
-                                       selectTab = tab;
-                               }
-                       }.bind(this));
-                       
-                       if (selectTab) {
-                               this.select(undefined, selectTab, false);
-                       }
-                       
-                       return !!selectTab;
-               },
-               
-               /**
-                * Rebuilds all tabs, must be invoked after adding or removing of tabs.
-                * 
-                * Warning: Do not remove tabs if you plan to add these later again or at least clone the nodes
-                *          to prevent issues with already bound event listeners. Consider hiding them via CSS.
-                */
-               rebuild: function() {
-                       var oldTabs = new Dictionary();
-                       oldTabs.merge(this._tabs);
-                       
-                       this.validate();
-                       this.init(oldTabs);
-               },
-               
-               /**
-                * Returns true if this tab menu has a tab with provided name.
-                * 
-                * @param       {string}        name    tab name
-                * @return      {boolean}       true if tab name matches
-                */
-               hasTab: function (name) {
-                       return this._tabs.has(name);
-               },
-               
-               /**
-                * Handles clicks on a tab.
-                * 
-                * @param       {object}        event   event object
-                */
-               _onClick: function(event) {
-                       event.preventDefault();
-                       
-                       this.select(null, event.currentTarget.parentNode);
-               },
-               
-               /**
-                * Returns the tab name.
-                * 
-                * @param       {Element}       tab     tab element
-                * @return      {string}        tab name
-                */
-               _getTabName: function(tab) {
-                       var name = elData(tab, 'name');
-                       
-                       // handle legacy tab menus
-                       if (!name) {
-                               if (tab.childElementCount === 1 && tab.children[0].nodeName === 'A') {
-                                       if (tab.children[0].href.match(/#([^#]+)$/)) {
-                                               name = RegExp.$1;
-                                               
-                                               if (elById(name) === null) {
-                                                       name = null;
-                                               }
-                                               else {
-                                                       this._isLegacy = true;
-                                                       elData(tab, 'name', name);
-                                               }
-                                       }
-                               }
-                       }
-                       
-                       return name;
-               },
-               
-               /**
-                * Returns the currently active tab.
-                *
-                * @return      {Element}       active tab
-                */
-               getActiveTab: function() {
-                       return elBySel('#' + this._container.id + ' > nav > ul > li.active');
-               },
-               
-               /**
-                * Returns the list of registered content containers.
-                * 
-                * @returns     {Dictionary}    content containers
-                */
-               getContainers: function() {
-                       return this._containers;
-               },
-               
-               /**
-                * Returns the list of registered tabs.
-                * 
-                * @returns     {Dictionary}    tab items
-                */
-               getTabs: function() {
-                       return this._tabs;
-               }
-       };
-       
-       TabMenuSimple.getIdentifierFromHash = function () {
-               if (window.location.hash.match(/^#+([^\/]+)+(?:\/.+)?/)) {
-                       return RegExp.$1;
-               }
-               
-               return '';
-       };
-       
-       return TabMenuSimple;
-});
-
-/**
- * Common interface for tab menu access.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/TabMenu
- */
-define('WoltLabSuite/Core/Ui/TabMenu',['Dictionary', 'EventHandler', 'Dom/ChangeListener', 'Dom/Util', 'Ui/CloseOverlay', 'Ui/Screen', './TabMenu/Simple'], function(Dictionary, EventHandler, DomChangeListener, DomUtil, UiCloseOverlay, UiScreen, SimpleTabMenu) {
-       "use strict";
-       
-       var _activeList = null;
-       var _enableTabScroll = false;
-       var _tabMenus = new Dictionary();
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/TabMenu
-        */
-       return {
-               /**
-                * Sets up tab menus and binds listeners.
-                */
-               setup: function() {
-                       this._init();
-                       this._selectErroneousTabs();
-                       
-                       DomChangeListener.add('WoltLabSuite/Core/Ui/TabMenu', this._init.bind(this));
-                       UiCloseOverlay.add('WoltLabSuite/Core/Ui/TabMenu', function() {
-                               if (_activeList) {
-                                       _activeList.classList.remove('active');
-                                       
-                                       _activeList = null;
-                               }
-                       });
-                       
-                       //noinspection JSUnresolvedVariable
-                       UiScreen.on('screen-sm-down', {
-                               enable: this._scrollEnable.bind(this, false),
-                               disable: this._scrollDisable.bind(this),
-                               setup: this._scrollEnable.bind(this, true)
-                       });
-                       
-                       window.addEventListener('hashchange', function () {
-                               var hash = SimpleTabMenu.getIdentifierFromHash();
-                               var element = (hash) ? elById(hash) : null;
-                               if (element !== null && element.classList.contains('tabMenuContent')) {
-                                       _tabMenus.forEach(function (tabMenu) {
-                                               if (tabMenu.hasTab(hash)) {
-                                                       tabMenu.select(hash);
-                                               }
-                                       });
-                               }
-                       });
-                       
-                       var hash = SimpleTabMenu.getIdentifierFromHash();
-                       if (hash) {
-                               window.setTimeout(function () {
-                                       // check if page was initially scrolled using a tab id
-                                       var tabMenuContent = elById(hash);
-                                       if (tabMenuContent && tabMenuContent.classList.contains('tabMenuContent')) {
-                                               var scrollY = (window.scrollY || window.pageYOffset);
-                                               if (scrollY > 0) {
-                                                       var parent = tabMenuContent.parentNode;
-                                                       var offsetTop = parent.offsetTop - 50;
-                                                       if (offsetTop < 0) offsetTop = 0;
-                                                       
-                                                       if (scrollY > offsetTop) {
-                                                               var y = DomUtil.offset(parent).top;
-                                                               
-                                                               if (y <= 50) {
-                                                                       y = 0;
-                                                               }
-                                                               else {
-                                                                       y -= 50;
-                                                               }
-                                                               
-                                                               window.scrollTo(0, y);
-                                                       }
-                                               }
-                                       }
-                               }, 100);
-                       }
-               },
-               
-               /**
-                * Initializes available tab menus.
-                */
-               _init: function() {
-                       var container, containerId, list, returnValue, tabMenu, tabMenus = elBySelAll('.tabMenuContainer:not(.staticTabMenuContainer)');
-                       for (var i = 0, length = tabMenus.length; i < length; i++) {
-                               container = tabMenus[i];
-                               containerId = DomUtil.identify(container);
-                               
-                               if (_tabMenus.has(containerId)) {
-                                       continue;
-                               }
-                               
-                               tabMenu = new SimpleTabMenu(container);
-                               if (tabMenu.validate()) {
-                                       returnValue = tabMenu.init();
-                                       
-                                       _tabMenus.set(containerId, tabMenu);
-                                       
-                                       if (returnValue instanceof Element) {
-                                               tabMenu = this.getTabMenu(returnValue.parentNode.id);
-                                               tabMenu.select(returnValue.id, null, true);
-                                       }
-                                       
-                                       list = elBySel('#' + containerId + ' > nav > ul');
-                                       (function(list) {
-                                               list.addEventListener(WCF_CLICK_EVENT, function(event) {
-                                                       event.preventDefault();
-                                                       event.stopPropagation();
-                                                       
-                                                       if (event.target === list) {
-                                                               list.classList.add('active');
-                                                               
-                                                               _activeList = list;
-                                                       }
-                                                       else {
-                                                               list.classList.remove('active');
-                                                               
-                                                               _activeList = null;
-                                                       }
-                                               });
-                                       })(list);
-                                       
-                                       // bind scroll listener
-                                       elBySelAll('.tabMenu, .menu', container, (function(menu) {
-                                               var callback = this._rebuildMenuOverflow.bind(this, menu);
-                                               
-                                               var timeout = null;
-                                               elBySel('ul', menu).addEventListener('scroll', function () {
-                                                       if (timeout !== null) {
-                                                               window.clearTimeout(timeout);
-                                                       }
-                                                       
-                                                       // slight delay to avoid calling this function too often
-                                                       timeout = window.setTimeout(callback, 10);
-                                               });
-                                       }).bind(this));
-                               }
-                       }
-               },
-               
-               /**
-                * Selects the first tab containing an element with class `formError`.
-                */
-               _selectErroneousTabs: function() {
-                       _tabMenus.forEach(function(tabMenu) {
-                               var foundError = false;
-                               tabMenu.getContainers().forEach(function(container) {
-                                       if (!foundError && elByClass('formError', container).length) {
-                                               foundError = true;
-                                               
-                                               tabMenu.select(container.id);
-                                       }
-                               });
-                       });
-               },
-               
-               /**
-                * Returns a SimpleTabMenu instance for given container id.
-                * 
-                * @param       {string}        containerId     tab menu container id
-                * @return      {(SimpleTabMenu|undefined)}     tab menu object
-                */
-               getTabMenu: function(containerId) {
-                       return _tabMenus.get(containerId);
-               },
-               
-               _scrollEnable: function (isSetup) {
-                       _enableTabScroll = true;
-                       
-                       _tabMenus.forEach((function (tabMenu) {
-                               var activeTab = tabMenu.getActiveTab();
-                               if (isSetup) {
-                                       this._rebuildMenuOverflow(activeTab.closest('.menu, .tabMenu'));
-                               }
-                               else {
-                                       this.scrollToTab(activeTab);
-                               }
-                       }).bind(this));
-               },
-               
-               _scrollDisable: function () {
-                       _enableTabScroll = false;
-               },
-               
-               scrollToTab: function (tab) {
-                       if (!_enableTabScroll) {
-                               return;
-                       }
-                       
-                       var list = tab.closest('ul');
-                       var width = list.clientWidth;
-                       var scrollLeft = list.scrollLeft;
-                       var scrollWidth = list.scrollWidth;
-                       if (width === scrollWidth) {
-                               // no overflow, ignore
-                               return;
-                       }
-                       
-                       // check if tab is currently visible
-                       var left = tab.offsetLeft;
-                       var shouldScroll = false;
-                       if (left < scrollLeft) {
-                               shouldScroll = true;
-                       }
-                       
-                       var paddingRight = false;
-                       if (!shouldScroll) {
-                               var visibleWidth = width - (left - scrollLeft);
-                               var virtualWidth = tab.clientWidth;
-                               if (tab.nextElementSibling !== null) {
-                                       paddingRight = true;
-                                       virtualWidth += 20;
-                               }
-                               
-                               if (visibleWidth < virtualWidth) {
-                                       shouldScroll = true;
-                               }
-                       }
-                       
-                       if (shouldScroll) {
-                               this._scrollMenu(list, left, scrollLeft, scrollWidth, width, paddingRight);
-                       }
-               },
-               
-               _scrollMenu: function (list, left, scrollLeft, scrollWidth, width, paddingRight) {
-                       // allow some padding to indicate overflow
-                       if (paddingRight) {
-                               left -= 15;
-                       }
-                       else if (left > 0) {
-                               left -= 15;
-                       }
-                       
-                       if (left < 0) {
-                               left = 0;
-                       }
-                       else {
-                               // ensure that our left value is always within the boundaries
-                               left = Math.min(left, scrollWidth - width);
-                       }
-                       
-                       if (scrollLeft === left) {
-                               return;
-                       }
-                       
-                       list.classList.add('enableAnimation');
-                       
-                       // new value is larger, we're scrolling towards the end
-                       if (scrollLeft < left) {
-                               list.firstElementChild.style.setProperty('margin-left', (scrollLeft - left) + 'px', '');
-                       }
-                       else {
-                               // new value is smaller, we're scrolling towards the start
-                               list.style.setProperty('padding-left', (scrollLeft - left) + 'px', '');
-                       }
-                       
-                       setTimeout(function () {
-                               list.classList.remove('enableAnimation');
-                               
-                               list.firstElementChild.style.removeProperty('margin-left');
-                               list.style.removeProperty('padding-left');
-                               
-                               list.scrollLeft = left;
-                       }, 300);
-               },
-               
-               _rebuildMenuOverflow: function (menu) {
-                       if (!_enableTabScroll) {
-                               return;
-                       }
-                       
-                       var width = menu.clientWidth;
-                       var list = elBySel('ul', menu);
-                       var scrollLeft = list.scrollLeft;
-                       var scrollWidth = list.scrollWidth;
-                       
-                       var overflowLeft = (scrollLeft > 0);
-                       var overlayLeft = elBySel('.tabMenuOverlayLeft', menu);
-                       if (overflowLeft) {
-                               if (overlayLeft === null) {
-                                       overlayLeft = elCreate('span');
-                                       overlayLeft.className = 'tabMenuOverlayLeft icon icon24 fa-angle-left';
-                                       overlayLeft.addEventListener(WCF_CLICK_EVENT, (function () {
-                                               var listWidth = list.clientWidth;
-                                               
-                                               this._scrollMenu(
-                                                       list,
-                                                       list.scrollLeft - ~~(listWidth / 2),
-                                                       list.scrollLeft,
-                                                       list.scrollWidth,
-                                                       listWidth,
-                                                       0
-                                               );
-                                       }).bind(this));
-                                       
-                                       menu.insertBefore(overlayLeft, menu.firstChild);
-                               }
-                               
-                               overlayLeft.classList.add('active');
-                       }
-                       else if (overlayLeft !== null) {
-                               overlayLeft.classList.remove('active');
-                       }
-                       
-                       var overflowRight = (width + scrollLeft < scrollWidth);
-                       var overlayRight = elBySel('.tabMenuOverlayRight', menu);
-                       if (overflowRight) {
-                               if (overlayRight === null) {
-                                       overlayRight = elCreate('span');
-                                       overlayRight.className = 'tabMenuOverlayRight icon icon24 fa-angle-right';
-                                       overlayRight.addEventListener(WCF_CLICK_EVENT, (function () {
-                                               var listWidth = list.clientWidth;
-                                               
-                                               this._scrollMenu(
-                                                       list,
-                                                       list.scrollLeft + ~~(listWidth / 2),
-                                                       list.scrollLeft,
-                                                       list.scrollWidth,
-                                                       listWidth,
-                                                       0
-                                               );
-                                       }).bind(this));
-                                       
-                                       menu.appendChild(overlayRight);
-                               }
-                               
-                               overlayRight.classList.add('active');
-                       }
-                       else if (overlayRight !== null) {
-                               overlayRight.classList.remove('active');
-                       }
-               }
-       };
-});
-
-/**
- * Dynamically transforms menu-like structures to handle items exceeding the available width
- * by moving them into a separate dropdown.  
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/FlexibleMenu
- */
-define('WoltLabSuite/Core/Ui/FlexibleMenu',['Core', 'Dictionary', 'Dom/ChangeListener', 'Dom/Traverse', 'Dom/Util', 'Ui/SimpleDropdown'], function(Core, Dictionary, DomChangeListener, DomTraverse, DomUtil, SimpleDropdown) {
-       "use strict";
-       
-       var _containers = new Dictionary();
-       var _dropdowns = new Dictionary();
-       var _dropdownMenus = new Dictionary();
-       var _itemLists = new Dictionary();
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/FlexibleMenu
-        */
-       var UiFlexibleMenu = {
-               /**
-                * Register default menus and set up event listeners.
-                */
-               setup: function() {
-                       if (elById('mainMenu') !== null) this.register('mainMenu');
-                       var navigationHeader = elBySel('.navigationHeader');
-                       if (navigationHeader !== null) this.register(DomUtil.identify(navigationHeader));
-                       
-                       window.addEventListener('resize', this.rebuildAll.bind(this));
-                       DomChangeListener.add('WoltLabSuite/Core/Ui/FlexibleMenu', this.registerTabMenus.bind(this));
-               },
-               
-               /**
-                * Registers a menu by element id.
-                * 
-                * @param       {string}        containerId     element id
-                */
-               register: function(containerId) {
-                       var container = elById(containerId);
-                       if (container === null) {
-                               throw "Expected a valid element id, '" + containerId + "' does not exist.";
-                       }
-                       
-                       if (_containers.has(containerId)) {
-                               return;
-                       }
-                       
-                       var list = DomTraverse.childByTag(container, 'UL');
-                       if (list === null) {
-                               throw "Expected an <ul> element as child of container '" + containerId + "'.";
-                       }
-                       
-                       _containers.set(containerId, container);
-                       _itemLists.set(containerId, list);
-                       
-                       this.rebuild(containerId);
-               },
-               
-               /**
-                * Registers tab menus.
-                */
-               registerTabMenus: function() {
-                       var tabMenus = elBySelAll('.tabMenuContainer:not(.jsFlexibleMenuEnabled), .messageTabMenu:not(.jsFlexibleMenuEnabled)');
-                       for (var i = 0, length = tabMenus.length; i < length; i++) {
-                               var tabMenu = tabMenus[i];
-                               var nav = DomTraverse.childByTag(tabMenu, 'NAV');
-                               if (nav !== null) {
-                                       tabMenu.classList.add('jsFlexibleMenuEnabled');
-                                       this.register(DomUtil.identify(nav));
-                               }
-                       }
-               },
-               
-               /**
-                * Rebuilds all menus, e.g. on window resize.
-                */
-               rebuildAll: function() {
-                       _containers.forEach((function(container, containerId) {
-                               this.rebuild(containerId);
-                       }).bind(this));
-               },
-               
-               /**
-                * Rebuild the menu identified by given element id.
-                * 
-                * @param       {string}        containerId     element id
-                */
-               rebuild: function(containerId) {
-                       var container = _containers.get(containerId);
-                       if (container === undefined) {
-                               throw "Expected a valid element id, '" + containerId + "' is unknown.";
-                       }
-                       
-                       var styles = window.getComputedStyle(container);
-                       
-                       var availableWidth = container.parentNode.clientWidth;
-                       availableWidth -= DomUtil.styleAsInt(styles, 'margin-left');
-                       availableWidth -= DomUtil.styleAsInt(styles, 'margin-right');
-                       
-                       var list = _itemLists.get(containerId);
-                       var items = DomTraverse.childrenByTag(list, 'LI');
-                       var dropdown = _dropdowns.get(containerId);
-                       var dropdownWidth = 0;
-                       if (dropdown !== undefined) {
-                               // show all items for calculation
-                               for (var i = 0, length = items.length; i < length; i++) {
-                                       var item = items[i];
-                                       if (item.classList.contains('dropdown')) {
-                                               continue;
-                                       }
-                                       
-                                       elShow(item);
-                               }
-                               
-                               if (dropdown.parentNode !== null) {
-                                       dropdownWidth = DomUtil.outerWidth(dropdown);
-                               }
-                       }
-                       
-                       var currentWidth = list.scrollWidth - dropdownWidth;
-                       var hiddenItems = [];
-                       if (currentWidth > availableWidth) {
-                               // hide items starting with the last one
-                               for (var i = items.length - 1; i >= 0; i--) {
-                                       var item = items[i];
-                                       
-                                       // ignore dropdown and active item
-                                       if (item.classList.contains('dropdown') || item.classList.contains('active') || item.classList.contains('ui-state-active')) {
-                                               continue;
-                                       }
-                                       
-                                       hiddenItems.push(item);
-                                       elHide(item);
-                                       
-                                       if (list.scrollWidth < availableWidth) {
-                                               break;
-                                       }
-                               }
-                       }
-                       
-                       if (hiddenItems.length) {
-                               var dropdownMenu;
-                               if (dropdown === undefined) {
-                                       dropdown = elCreate('li');
-                                       dropdown.className = 'dropdown jsFlexibleMenuDropdown';
-                                       var icon = elCreate('a');
-                                       icon.className = 'icon icon16 fa-list';
-                                       dropdown.appendChild(icon);
-                                       
-                                       dropdownMenu = elCreate('ul');
-                                       dropdownMenu.classList.add('dropdownMenu');
-                                       dropdown.appendChild(dropdownMenu);
-                                       
-                                       _dropdowns.set(containerId, dropdown);
-                                       _dropdownMenus.set(containerId, dropdownMenu);
-                                       
-                                       SimpleDropdown.init(icon);
-                               }
-                               else {
-                                       dropdownMenu = _dropdownMenus.get(containerId);
-                               }
-                               
-                               if (dropdown.parentNode === null) {
-                                       list.appendChild(dropdown);
-                               }
-                               
-                               // build dropdown menu
-                               var fragment = document.createDocumentFragment();
-                               
-                               var self = this;
-                               hiddenItems.forEach(function(hiddenItem) {
-                                       var item = elCreate('li');
-                                       item.innerHTML = hiddenItem.innerHTML;
-                                       
-                                       item.addEventListener(WCF_CLICK_EVENT, (function(event) {
-                                               event.preventDefault();
-                                               
-                                               Core.triggerEvent(elBySel('a', hiddenItem), WCF_CLICK_EVENT);
-                                               
-                                               // force a rebuild to guarantee the active item being visible
-                                               setTimeout(function() {
-                                                       self.rebuild(containerId);
-                                               }, 59);
-                                       }).bind(this));
-                                       
-                                       fragment.appendChild(item);
-                               });
-                               
-                               dropdownMenu.innerHTML = '';
-                               dropdownMenu.appendChild(fragment);
-                       }
-                       else if (dropdown !== undefined && dropdown.parentNode !== null) {
-                               elRemove(dropdown);
-                       }
-               }
-       };
-       
-       return UiFlexibleMenu;
-});
-
-/**
- * Provides enhanced tooltips.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Tooltip
- */
-define('WoltLabSuite/Core/Ui/Tooltip',['Environment', 'Dom/ChangeListener', 'Ui/Alignment'], function(Environment, DomChangeListener, UiAlignment) {
-       "use strict";
-       
-       var _callbackMouseEnter = null;
-       var _callbackMouseLeave = null;
-       var _elements = null;
-       var _pointer = null;
-       var _text = null;
-       var _tooltip = null;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Tooltip
-        */
-       return {
-               /**
-                * Initializes the tooltip element and binds event listener.
-                */
-               setup: function() {
-                       if (Environment.platform() !== 'desktop') return;
-                       
-                       _tooltip = elCreate('div');
-                       elAttr(_tooltip, 'id', 'balloonTooltip');
-                       _tooltip.classList.add('balloonTooltip');
-                       _tooltip.addEventListener('transitionend', function () {
-                               if (!_tooltip.classList.contains('active')) {
-                                       // reset back to the upper left corner, prevent it from staying outside
-                                       // the viewport if the body overflow was previously hidden
-                                       ['bottom', 'left', 'right', 'top'].forEach(function(property) {
-                                               _tooltip.style.removeProperty(property);
-                                       });
-                               }
-                       });
-                       
-                       _text = elCreate('span');
-                       elAttr(_text, 'id', 'balloonTooltipText');
-                       _tooltip.appendChild(_text);
-                       
-                       _pointer = elCreate('span');
-                       _pointer.classList.add('elementPointer');
-                       _pointer.appendChild(elCreate('span'));
-                       _tooltip.appendChild(_pointer);
-                       
-                       document.body.appendChild(_tooltip);
-                       
-                       _elements = elByClass('jsTooltip');
-                       
-                       _callbackMouseEnter = this._mouseEnter.bind(this);
-                       _callbackMouseLeave = this._mouseLeave.bind(this);
-                       
-                       this.init();
-                       
-                       DomChangeListener.add('WoltLabSuite/Core/Ui/Tooltip', this.init.bind(this));
-                       window.addEventListener('scroll', this._mouseLeave.bind(this));
-               },
-               
-               /**
-                * Initializes tooltip elements.
-                */
-               init: function() {
-                       if (_elements.length === 0) {
-                               return;
-                       }
-                       
-                       elBySelAll('.jsTooltip', undefined, function (element) {
-                               element.classList.remove('jsTooltip');
-                               
-                               var title = elAttr(element, 'title').trim();
-                               if (title.length) {
-                                       elData(element, 'tooltip', title);
-                                       element.removeAttribute('title');
-                                       elAttr(element, 'aria-label', title);
-                                       
-                                       element.addEventListener('mouseenter', _callbackMouseEnter);
-                                       element.addEventListener('mouseleave', _callbackMouseLeave);
-                                       element.addEventListener(WCF_CLICK_EVENT, _callbackMouseLeave);
-                               }
-                       });
-               },
-               
-               /**
-                * Displays the tooltip on mouse enter.
-                * 
-                * @param       {Event}         event   event object
-                */
-               _mouseEnter: function(event) {
-                       var element = event.currentTarget;
-                       var title = elAttr(element, 'title');
-                       title = (typeof title === 'string') ? title.trim() : '';
-                       
-                       if (title !== '') {
-                               elData(element, 'tooltip', title);
-                               element.removeAttribute('title');
-                       }
-                       
-                       title = elData(element, 'tooltip');
-                       
-                       // reset tooltip position
-                       _tooltip.style.removeProperty('top');
-                       _tooltip.style.removeProperty('left');
-                       
-                       // ignore empty tooltip
-                       if (!title.length) {
-                               _tooltip.classList.remove('active');
-                               return;
-                       }
-                       else {
-                               _tooltip.classList.add('active');
-                       }
-                       
-                       _text.textContent = title;
-                       
-                       UiAlignment.set(_tooltip, element, {
-                               horizontal: 'center',
-                               verticalOffset: 4,
-                               pointer: true,
-                               pointerClassNames: ['inverse'],
-                               vertical: 'top'
-                       });
-               },
-               
-               /**
-                * Hides the tooltip once the mouse leaves the element.
-                */
-               _mouseLeave: function() {
-                       _tooltip.classList.remove('active');
-               }
-       };
-});
-
-/**
- * Date picker with time support.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Date/Picker
- */
-define('WoltLabSuite/Core/Date/Picker',['DateUtil', 'Dom/Traverse', 'Dom/Util', 'EventHandler', 'Language', 'ObjectMap', 'Dom/ChangeListener', 'Ui/Alignment', 'WoltLabSuite/Core/Ui/CloseOverlay'], function(DateUtil, DomTraverse, DomUtil, EventHandler, Language, ObjectMap, DomChangeListener, UiAlignment, UiCloseOverlay) {
-       "use strict";
-       
-       var _didInit = false;
-       var _firstDayOfWeek = 0;
-       var _wasInsidePicker = false;
-       
-       var _data = new ObjectMap();
-       var _input = null;
-       var _maxDate = 0;
-       var _minDate = 0;
-       
-       var _dateCells = [];
-       var _dateGrid = null;
-       var _dateHour = null;
-       var _dateMinute = null;
-       var _dateMonth = null;
-       var _dateMonthNext = null;
-       var _dateMonthPrevious = null;
-       var _dateTime = null;
-       var _dateYear = null;
-       var _datePicker = null;
-       
-       var _callbackOpen = null;
-       var _callbackFocus = null;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Date/Picker
-        */
-       var DatePicker = {
-               /**
-                * Initializes all date and datetime input fields.
-                */
-               init: function() {
-                       this._setup();
-                       
-                       var elements = elBySelAll('input[type="date"]:not(.inputDatePicker), input[type="datetime"]:not(.inputDatePicker)');
-                       var now = new Date();
-                       for (var i = 0, length = elements.length; i < length; i++) {
-                               var element = elements[i];
-                               element.classList.add('inputDatePicker');
-                               element.readOnly = true;
-                               
-                               var isDateTime = (elAttr(element, 'type') === 'datetime');
-                               var isTimeOnly = (isDateTime && elDataBool(element, 'time-only'));
-                               var disableClear = elDataBool(element, 'disable-clear');
-                               var ignoreTimezone = isDateTime && elDataBool(element, 'ignore-timezone');
-                               var isBirthday = element.classList.contains('birthday');
-                               
-                               elData(element, 'is-date-time', isDateTime);
-                               elData(element, 'is-time-only', isTimeOnly);
-                               
-                               // convert value
-                               var date = null, value = elAttr(element, 'value');
-                               
-                               // ignore the timezone, if the value is only a date (YYYY-MM-DD)
-                               var isDateOnly = /^\d+-\d+-\d+$/.test(value);
-                               
-                               if (elAttr(element, 'value')) {
-                                       if (isTimeOnly) {
-                                               date = new Date();
-                                               var tmp = value.split(':');
-                                               date.setHours(tmp[0], tmp[1]);
-                                       }
-                                       else {
-                                               if (ignoreTimezone || isBirthday || isDateOnly) {
-                                                       var timezoneOffset = new Date(value).getTimezoneOffset();
-                                                       var timezone = (timezoneOffset > 0) ? '-' : '+'; // -120 equals GMT+0200
-                                                       timezoneOffset = Math.abs(timezoneOffset);
-                                                       var hours = (Math.floor(timezoneOffset / 60)).toString();
-                                                       var minutes = (timezoneOffset % 60).toString();
-                                                       timezone += (hours.length === 2) ? hours : '0' + hours;
-                                                       timezone += ':';
-                                                       timezone += (minutes.length === 2) ? minutes : '0' + minutes;
-                                                       
-                                                       if (isBirthday || isDateOnly) {
-                                                               value += 'T00:00:00' + timezone;
-                                                       }
-                                                       else {
-                                                               value = value.replace(/[+-][0-9]{2}:[0-9]{2}$/, timezone);
-                                                       }
-                                               }
-                                               
-                                               date = new Date(value);
-                                       }
-                                       
-                                       var time = date.getTime();
-                                       
-                                       // check for invalid dates
-                                       if (isNaN(time)) {
-                                               value = '';
-                                       }
-                                       else {
-                                               elData(element, 'value', time);
-                                               var format = (isTimeOnly) ? 'formatTime' : ('formatDate' + (isDateTime ? 'Time' : ''));
-                                               value = DateUtil[format](date);
-                                       }
-                               }
-                               
-                               var isEmpty = (value.length === 0);
-                               
-                               // handle birthday input
-                               if (isBirthday) {
-                                       elData(element, 'min-date', '120');
-                                       
-                                       // do not use 'now' here, all though it makes sense, it causes bad UX 
-                                       elData(element, 'max-date', new Date().getFullYear() + '-12-31');
-                               }
-                               else {
-                                       if (element.min) elData(element, 'min-date', element.min);
-                                       if (element.max) elData(element, 'max-date', element.max);
-                               }
-                               
-                               this._initDateRange(element, now, true);
-                               this._initDateRange(element, now, false);
-                               
-                               if (elData(element, 'min-date') === elData(element, 'max-date')) {
-                                       throw new Error("Minimum and maximum date cannot be the same (element id '" + element.id + "').");
-                               }
-                               
-                               // change type to prevent browser's datepicker to trigger
-                               element.type = 'text';
-                               element.value = value;
-                               elData(element, 'empty', isEmpty);
-                               
-                               if (elData(element, 'placeholder')) {
-                                       elAttr(element, 'placeholder', elData(element, 'placeholder'));
-                               }
-                               
-                               // add a hidden element to hold the actual date
-                               var shadowElement = elCreate('input');
-                               shadowElement.id = element.id + 'DatePicker';
-                               shadowElement.name = element.name;
-                               shadowElement.type = 'hidden';
-                               
-                               if (date !== null) {
-                                       if (isTimeOnly) {
-                                               shadowElement.value = DateUtil.format(date, 'H:i');
-                                       }
-                                       else if (ignoreTimezone) {
-                                               shadowElement.value = DateUtil.format(date, 'Y-m-dTH:i:s');
-                                       }
-                                       else {
-                                               shadowElement.value = DateUtil.format(date, (isDateTime) ? 'c' : 'Y-m-d');
-                                       }
-                               }
-                               
-                               element.parentNode.insertBefore(shadowElement, element);
-                               element.removeAttribute('name');
-                               
-                               element.addEventListener(WCF_CLICK_EVENT, _callbackOpen);
-                               
-                               if (!element.disabled) {
-                                       // create input addon
-                                       var container = elCreate('div');
-                                       container.className = 'inputAddon';
-                                       
-                                       var button = elCreate('a');
-                                       
-                                       button.className = 'inputSuffix button jsTooltip';
-                                       button.href = '#';
-                                       elAttr(button, 'role', 'button');
-                                       elAttr(button, 'tabindex', '0');
-                                       elAttr(button, 'title', Language.get('wcf.date.datePicker'));
-                                       elAttr(button, 'aria-label', Language.get('wcf.date.datePicker'));
-                                       elAttr(button, 'aria-haspopup', true);
-                                       elAttr(button, 'aria-expanded', false);
-                                       button.addEventListener(WCF_CLICK_EVENT, _callbackOpen);
-                                       container.appendChild(button);
-                                       
-                                       var icon = elCreate('span');
-                                       icon.className = 'icon icon16 fa-calendar';
-                                       button.appendChild(icon);
-                                       
-                                       element.parentNode.insertBefore(container, element);
-                                       container.insertBefore(element, button);
-                                       
-                                       if (!disableClear) {
-                                               button = elCreate('a');
-                                               button.className = 'inputSuffix button';
-                                               button.addEventListener(WCF_CLICK_EVENT, this.clear.bind(this, element));
-                                               if (isEmpty) button.style.setProperty('visibility', 'hidden', '');
-                                               
-                                               container.appendChild(button);
-                                               
-                                               icon = elCreate('span');
-                                               icon.className = 'icon icon16 fa-times';
-                                               button.appendChild(icon);
-                                       }
-                               }
-                               
-                               // check if the date input has one of the following classes set otherwise default to 'short'
-                               var hasClass = false, knownClasses = ['tiny', 'short', 'medium', 'long'];
-                               for (var j = 0; j < 4; j++) {
-                                       if (element.classList.contains(knownClasses[j])) {
-                                               hasClass = true;
-                                       }
-                               }
-                               
-                               if (!hasClass) {
-                                       element.classList.add('short');
-                               }
-                               
-                               _data.set(element, {
-                                       clearButton: button,
-                                       shadow: shadowElement,
-                                       
-                                       disableClear: disableClear,
-                                       isDateTime: isDateTime,
-                                       isEmpty: isEmpty,
-                                       isTimeOnly: isTimeOnly,
-                                       ignoreTimezone: ignoreTimezone,
-                                       
-                                       onClose: null
-                               });
-                       }
-               },
-               
-               /**
-                * Initializes the minimum/maximum date range.
-                * 
-                * @param       {Element}       element         input element
-                * @param       {Date}          now             current date
-                * @param       {boolean}       isMinDate       true for the minimum date
-                */
-               _initDateRange: function(element, now, isMinDate) {
-                       var attribute = 'data-' + (isMinDate ? 'min' : 'max') + '-date';
-                       var value = (element.hasAttribute(attribute)) ? elAttr(element, attribute).trim() : '';
-                       
-                       if (value.match(/^(\d{4})-(\d{2})-(\d{2})$/)) {
-                               // YYYY-mm-dd
-                               value = new Date(value).getTime();
-                       }
-                       else if (value === 'now') {
-                               value = now.getTime();
-                       }
-                       else if (value.match(/^\d{1,3}$/)) {
-                               // relative time span in years
-                               var date = new Date(now.getTime());
-                               date.setFullYear(date.getFullYear() + ~~value * (isMinDate ? -1 : 1));
-                               
-                               value = date.getTime();
-                       }
-                       else if (value.match(/^datePicker-(.+)$/)) {
-                               // element id, e.g. `datePicker-someOtherElement`
-                               value = RegExp.$1;
-                               
-                               if (elById(value) === null) {
-                                       throw new Error("Reference date picker identified by '" + value + "' does not exists (element id: '" + element.id + "').");
-                               }
-                       }
-                       else if (/^\d{4}\-\d{2}\-\d{2}T/.test(value)) {
-                               value = new Date(value).getTime();
-                       }
-                       else {
-                               value = new Date((isMinDate ? 1902 : 2038), 0, 1).getTime();
-                       }
-                       
-                       elAttr(element, attribute, value);
-               },
-               
-               /**
-                * Sets up callbacks and event listeners.
-                */
-               _setup: function() {
-                       if (_didInit) return;
-                       _didInit = true;
-                       
-                       _firstDayOfWeek = ~~Language.get('wcf.date.firstDayOfTheWeek');
-                       _callbackOpen = this._open.bind(this);
-                       
-                       DomChangeListener.add('WoltLabSuite/Core/Date/Picker', this.init.bind(this));
-                       UiCloseOverlay.add('WoltLabSuite/Core/Date/Picker', this._close.bind(this));
-               },
-               
-               /**
-                * Opens the date picker.
-                * 
-                * @param       {object}        event           event object
-                */
-               _open: function(event) {
-                       event.preventDefault();
-                       event.stopPropagation();
-                       
-                       this._createPicker();
-                       
-                       if (_callbackFocus === null) {
-                               _callbackFocus = this._maintainFocus.bind(this);
-                               document.body.addEventListener('focus', _callbackFocus, { capture: true });
-                       }
-                       
-                       var input = (event.currentTarget.nodeName === 'INPUT') ? event.currentTarget : event.currentTarget.previousElementSibling;
-                       if (input === _input) {
-                               this._close();
-                               return;
-                       }
-                       
-                       var dialogContent = DomTraverse.parentByClass(input, 'dialogContent');
-                       if (dialogContent !== null) {
-                               if (!elDataBool(dialogContent, 'has-datepicker-scroll-listener')) {
-                                       dialogContent.addEventListener('scroll', this._onDialogScroll.bind(this));
-                                       elData(dialogContent, 'has-datepicker-scroll-listener', 1);
-                               }
-                       }
-                       
-                       _input = input;
-                       var data = _data.get(_input), date, value = elData(_input, 'value');
-                       if (value) {
-                               date = new Date(+value);
-                               
-                               if (date.toString() === 'Invalid Date') {
-                                       date = new Date();
-                               }
-                       }
-                       else {
-                               date = new Date();
-                       }
-                       
-                       // set min/max date
-                       _minDate = elData(_input, 'min-date');
-                       if (_minDate.match(/^datePicker-(.+)$/)) _minDate = elData(elById(RegExp.$1), 'value');
-                       _minDate = new Date(+_minDate);
-                       
-                       _maxDate = elData(_input, 'max-date');
-                       if (_maxDate.match(/^datePicker-(.+)$/)) _maxDate = elData(elById(RegExp.$1), 'value');
-                       _maxDate = new Date(+_maxDate);
-                       
-                       if (data.isDateTime) {
-                               _dateHour.value = date.getHours();
-                               _dateMinute.value = date.getMinutes();
-                               
-                               _datePicker.classList.add('datePickerTime');
-                       }
-                       else {
-                               _datePicker.classList.remove('datePickerTime');
-                       }
-                       
-                       _datePicker.classList[(data.isTimeOnly) ? 'add' : 'remove']('datePickerTimeOnly');
-                       
-                       this._renderPicker(date.getDate(), date.getMonth(), date.getFullYear());
-                       
-                       UiAlignment.set(_datePicker, _input);
-                       
-                       elAttr(_input.nextElementSibling, 'aria-expanded', true);
-                       
-                       _wasInsidePicker = false;
-               },
-               
-               /**
-                * Closes the date picker.
-                */
-               _close: function() {
-                       if (_datePicker !== null && _datePicker.classList.contains('active')) {
-                               _datePicker.classList.remove('active');
-                               
-                               var data = _data.get(_input);
-                               if (typeof data.onClose === 'function') {
-                                       data.onClose();
-                               }
-                               
-                               EventHandler.fire('WoltLabSuite/Core/Date/Picker', 'close', {element: _input});
-                               
-                               elAttr(_input.nextElementSibling, 'aria-expanded', false);
-                               _input = null;
-                               _minDate = 0;
-                               _maxDate = 0;
-                       }
-               },
-               
-               /**
-                * Updates the position of the date picker in a dialog if the dialog content
-                * is scrolled.
-                * 
-                * @param       {Event}         event   scroll event
-                */
-               _onDialogScroll: function(event) {
-                       if (_input === null) {
-                               return;
-                       }
-                       
-                       var dialogContent = event.currentTarget;
-                       
-                       var offset = DomUtil.offset(_input);
-                       var dialogOffset = DomUtil.offset(dialogContent);
-                       
-                       // check if date picker input field is still (partially) visible
-                       if (offset.top + _input.clientHeight <= dialogOffset.top) {
-                               // top check
-                               this._close();
-                       }
-                       else if (offset.top >= dialogOffset.top + dialogContent.offsetHeight) {
-                               // bottom check
-                               this._close();
-                       }
-                       else if (offset.left <= dialogOffset.left) {
-                               // left check
-                               this._close();
-                       }
-                       else if (offset.left >= dialogOffset.left + dialogContent.offsetWidth) {
-                               // right check
-                               this._close();
-                       }
-                       else {
-                               UiAlignment.set(_datePicker, _input);
-                       }
-               },
-               
-               /**
-                * Renders the full picker on init.
-                * 
-                * @param       {int}           day
-                * @param       {int}           month
-                * @param       {int}           year
-                */
-               _renderPicker: function(day, month, year) {
-                       this._renderGrid(day, month, year);
-                       
-                       // create options for month and year
-                       var years = '';
-                       for (var i = _minDate.getFullYear(), last = _maxDate.getFullYear(); i <= last; i++) {
-                               years += '<option value="' + i + '">' + i + '</option>';
-                       }
-                       _dateYear.innerHTML = years;
-                       _dateYear.value = year;
-                       
-                       _dateMonth.value = month;
-                       
-                       _datePicker.classList.add('active');
-               },
-               
-               /**
-                * Updates the date grid.
-                * 
-                * @param       {int}           day
-                * @param       {int}           month
-                * @param       {int}           year
-                */
-               _renderGrid: function(day, month, year) {
-                       var cell, hasDay = (day !== undefined), hasMonth = (month !== undefined), i;
-                       
-                       day = ~~day || ~~elData(_dateGrid, 'day');
-                       month = ~~month;
-                       year = ~~year;
-                       
-                       // rebuild cells
-                       if (hasMonth || year) {
-                               var rebuildMonths = (year !== 0);
-                               
-                               // rebuild grid
-                               var fragment = document.createDocumentFragment();
-                               fragment.appendChild(_dateGrid);
-                               
-                               if (!hasMonth) month = ~~elData(_dateGrid, 'month');
-                               year = year || ~~elData(_dateGrid, 'year');
-                               
-                               // check if current selection exceeds min/max date
-                               var date = new Date(year + '-' + ('0' + (month + 1).toString()).slice(-2) + '-' + ('0' + day.toString()).slice(-2));
-                               if (date < _minDate) {
-                                       year = _minDate.getFullYear();
-                                       month = _minDate.getMonth();
-                                       day = _minDate.getDate();
-                                       
-                                       _dateMonth.value = month;
-                                       _dateYear.value = year;
-                                       
-                                       rebuildMonths = true;
-                               }
-                               else if (date > _maxDate) {
-                                       year = _maxDate.getFullYear();
-                                       month = _maxDate.getMonth();
-                                       day = _maxDate.getDate();
-                                       
-                                       _dateMonth.value = month;
-                                       _dateYear.value = year;
-                                       
-                                       rebuildMonths = true;
-                               }
-                               
-                               date = new Date(year + '-' + ('0' + (month + 1).toString()).slice(-2) + '-01');
-                               
-                               // shift until first displayed day equals first day of week
-                               while (date.getDay() !== _firstDayOfWeek) {
-                                       date.setDate(date.getDate() - 1);
-                               }
-                               
-                               // show the last row
-                               elShow(_dateCells[35].parentNode);
-                               
-                               var selectable;
-                               var comparableMinDate = new Date(_minDate.getFullYear(), _minDate.getMonth(), _minDate.getDate());
-                               for (i = 0; i < 42; i++) {
-                                       if (i === 35 && date.getMonth() !== month) {
-                                               // skip the last row if it only contains the next month
-                                               elHide(_dateCells[35].parentNode);
-                                               
-                                               break;
-                                       }
-                                       
-                                       cell = _dateCells[i];
-                                       
-                                       cell.textContent = date.getDate();
-                                       selectable = (date.getMonth() === month);
-                                       if (selectable) {
-                                               if (date < comparableMinDate) selectable = false;
-                                               else if (date > _maxDate) selectable = false;
-                                       }
-                                       
-                                       cell.classList[selectable ? 'remove' : 'add']('otherMonth');
-                                       if (selectable) {
-                                               cell.href = '#';
-                                               elAttr(cell, 'role', 'button');
-                                               elAttr(cell, 'tabindex', '0');
-                                               elAttr(cell, 'title', DateUtil.formatDate(date));
-                                               elAttr(cell, 'aria-label', DateUtil.formatDate(date));
-                                       }
-                                       
-                                       date.setDate(date.getDate() + 1);
-                               }
-                               
-                               elData(_dateGrid, 'month', month);
-                               elData(_dateGrid, 'year', year);
-                               
-                               _datePicker.insertBefore(fragment, _dateTime);
-                               
-                               if (!hasDay) {
-                                       // check if date is valid
-                                       date = new Date(year, month, day);
-                                       if (date.getDate() !== day) {
-                                               while (date.getMonth() !== month) {
-                                                       date.setDate(date.getDate() - 1);
-                                               }
-                                               
-                                               day = date.getDate();
-                                       }
-                               }
-                               
-                               if (rebuildMonths) {
-                                       for (i = 0; i < 12; i++) {
-                                               var currentMonth = _dateMonth.children[i];
-                                               
-                                               currentMonth.disabled = (year === _minDate.getFullYear() && currentMonth.value < _minDate.getMonth()) || (year === _maxDate.getFullYear() && currentMonth.value > _maxDate.getMonth());
-                                       }
-                                       
-                                       var nextMonth = new Date(year + '-' + ('0' + (month + 1).toString()).slice(-2) + '-01');
-                                       nextMonth.setMonth(nextMonth.getMonth() + 1);
-                                       
-                                       _dateMonthNext.classList[(nextMonth < _maxDate) ? 'add' : 'remove']('active');
-                                       
-                                       var previousMonth = new Date(year + '-' + ('0' + (month + 1).toString()).slice(-2) + '-01');
-                                       previousMonth.setDate(previousMonth.getDate() - 1);
-                                       
-                                       _dateMonthPrevious.classList[(previousMonth > _minDate) ? 'add' : 'remove']('active');
-                               }
-                       }
-                       
-                       // update active day
-                       if (day) {
-                               for (i = 0; i < 35; i++) {
-                                       cell = _dateCells[i];
-                                       
-                                       cell.classList[(!cell.classList.contains('otherMonth') && ~~cell.textContent === day) ? 'add' : 'remove']('active');
-                               }
-                               
-                               elData(_dateGrid, 'day', day);
-                       }
-                       
-                       this._formatValue();
-               },
-               
-               /**
-                * Sets the visible and shadow value
-                */
-               _formatValue: function() {
-                       var data = _data.get(_input), date;
-                       
-                       if (elData(_input, 'empty') === 'true') {
-                               return;
-                       }
-                       
-                       if (data.isDateTime) {
-                               date = new Date(
-                                       elData(_dateGrid, 'year'),
-                                       elData(_dateGrid, 'month'),
-                                       elData(_dateGrid, 'day'),
-                                       _dateHour.value,
-                                       _dateMinute.value
-                               );
-                       }
-                       else {
-                               date = new Date(
-                                       elData(_dateGrid, 'year'),
-                                       elData(_dateGrid, 'month'),
-                                       elData(_dateGrid, 'day')
-                               );
-                       }
-
-                       this.setDate(_input, date);
-               },
-               
-               /**
-                * Creates the date picker DOM.
-                */
-               _createPicker: function() {
-                       if (_datePicker !== null) {
-                               return;
-                       }
-                       
-                       _datePicker = elCreate('div');
-                       _datePicker.className = 'datePicker';
-                       _datePicker.addEventListener(WCF_CLICK_EVENT, function(event) { event.stopPropagation(); });
-                       
-                       var header = elCreate('header');
-                       _datePicker.appendChild(header);
-                       
-                       _dateMonthPrevious = elCreate('a');
-                       _dateMonthPrevious.className = 'previous jsTooltip';
-                       _dateMonthPrevious.href = '#';
-                       elAttr(_dateMonthPrevious, 'role', 'button');
-                       elAttr(_dateMonthPrevious, 'tabindex', '0');
-                       elAttr(_dateMonthPrevious, 'title', Language.get('wcf.date.datePicker.previousMonth'));
-                       elAttr(_dateMonthPrevious, 'aria-label', Language.get('wcf.date.datePicker.previousMonth'));
-                       _dateMonthPrevious.innerHTML = '<span class="icon icon16 fa-arrow-left"></span>';
-                       _dateMonthPrevious.addEventListener(WCF_CLICK_EVENT, this.previousMonth.bind(this));
-                       header.appendChild(_dateMonthPrevious);
-                       
-                       var monthYearContainer = elCreate('span');
-                       header.appendChild(monthYearContainer);
-                       
-                       _dateMonth = elCreate('select');
-                       _dateMonth.className = 'month jsTooltip';
-                       elAttr(_dateMonth, 'title', Language.get('wcf.date.datePicker.month'));
-                       elAttr(_dateMonth, 'aria-label', Language.get('wcf.date.datePicker.month'));
-                       _dateMonth.addEventListener('change', this._changeMonth.bind(this));
-                       monthYearContainer.appendChild(_dateMonth);
-                       
-                       var i, months = '', monthNames = Language.get('__monthsShort');
-                       for (i = 0; i < 12; i++) {
-                               months += '<option value="' + i + '">' + monthNames[i] + '</option>';
-                       }
-                       _dateMonth.innerHTML = months;
-                       
-                       _dateYear = elCreate('select');
-                       _dateYear.className = 'year jsTooltip';
-                       elAttr(_dateYear, 'title', Language.get('wcf.date.datePicker.year'));
-                       elAttr(_dateYear, 'aria-label', Language.get('wcf.date.datePicker.year'));
-                       _dateYear.addEventListener('change', this._changeYear.bind(this));
-                       monthYearContainer.appendChild(_dateYear);
-                       
-                       _dateMonthNext = elCreate('a');
-                       _dateMonthNext.className = 'next jsTooltip';
-                       _dateMonthNext.href = '#';
-                       elAttr(_dateMonthNext, 'role', 'button');
-                       elAttr(_dateMonthNext, 'tabindex', '0');
-                       elAttr(_dateMonthNext, 'title', Language.get('wcf.date.datePicker.nextMonth'));
-                       elAttr(_dateMonthNext, 'aria-label', Language.get('wcf.date.datePicker.nextMonth'));
-                       _dateMonthNext.innerHTML = '<span class="icon icon16 fa-arrow-right"></span>';
-                       _dateMonthNext.addEventListener(WCF_CLICK_EVENT, this.nextMonth.bind(this));
-                       header.appendChild(_dateMonthNext);
-                       
-                       _dateGrid = elCreate('ul');
-                       _datePicker.appendChild(_dateGrid);
-                       
-                       var item = elCreate('li');
-                       item.className = 'weekdays';
-                       _dateGrid.appendChild(item);
-                       
-                       var span, weekdays = Language.get('__daysShort');
-                       for (i = 0; i < 7; i++) {
-                               var day = i + _firstDayOfWeek;
-                               if (day > 6) day -= 7;
-                               
-                               span = elCreate('span');
-                               span.textContent = weekdays[day];
-                               item.appendChild(span);
-                       }
-                       
-                       // create date grid
-                       var callbackClick = this._click.bind(this), cell, row;
-                       for (i = 0; i < 6; i++) {
-                               row = elCreate('li');
-                               _dateGrid.appendChild(row);
-                               
-                               for (var j = 0; j < 7; j++) {
-                                       cell = elCreate('a');
-                                       cell.addEventListener(WCF_CLICK_EVENT, callbackClick);
-                                       _dateCells.push(cell);
-                                       
-                                       row.appendChild(cell);
-                               }
-                       }
-                       
-                       _dateTime = elCreate('footer');
-                       _datePicker.appendChild(_dateTime);
-                       
-                       _dateHour = elCreate('select');
-                       _dateHour.className = 'hour';
-                       elAttr(_dateHour, 'title', Language.get('wcf.date.datePicker.hour'));
-                       elAttr(_dateHour, 'aria-label', Language.get('wcf.date.datePicker.hour'));
-                       _dateHour.addEventListener('change', this._formatValue.bind(this));
-                       
-                       var tmp = '';
-                       var date = new Date(2000, 0, 1);
-                       var timeFormat = Language.get('wcf.date.timeFormat').replace(/:/, '').replace(/[isu]/g, '');
-                       for (i = 0; i < 24; i++) {
-                               date.setHours(i);
-                               tmp += '<option value="' + i + '">' + DateUtil.format(date, timeFormat) + "</option>";
-                       }
-                       _dateHour.innerHTML = tmp;
-                       
-                       _dateTime.appendChild(_dateHour);
-                       
-                       _dateTime.appendChild(document.createTextNode('\u00A0:\u00A0'));
-                       
-                       _dateMinute = elCreate('select');
-                       _dateMinute.className = 'minute';
-                       elAttr(_dateMinute, 'title', Language.get('wcf.date.datePicker.minute'));
-                       elAttr(_dateMinute, 'aria-label', Language.get('wcf.date.datePicker.minute'));
-                       _dateMinute.addEventListener('change', this._formatValue.bind(this));
-                       
-                       tmp = '';
-                       for (i = 0; i < 60; i++) {
-                               tmp += '<option value="' + i + '">' + (i < 10 ? '0' + i.toString() : i) + '</option>';
-                       }
-                       _dateMinute.innerHTML = tmp;
-                       
-                       _dateTime.appendChild(_dateMinute);
-                       
-                       document.body.appendChild(_datePicker);
-               },
-               
-               /**
-                * Shows the previous month.
-                */
-               previousMonth: function(event) {
-                       event.preventDefault();
-                       
-                       if (_dateMonth.value === '0') {
-                               _dateMonth.value = 11;
-                               _dateYear.value = ~~_dateYear.value - 1;
-                       }
-                       else {
-                               _dateMonth.value = ~~_dateMonth.value - 1;
-                       }
-                       
-                       this._renderGrid(undefined, _dateMonth.value, _dateYear.value);
-               },
-               
-               /**
-                * Shows the next month.
-                */
-               nextMonth: function(event) {
-                       event.preventDefault();
-                       
-                       if (_dateMonth.value === '11') {
-                               _dateMonth.value = 0;
-                               _dateYear.value = ~~_dateYear.value + 1;
-                       }
-                       else {
-                               _dateMonth.value = ~~_dateMonth.value + 1;
-                       }
-                       
-                       this._renderGrid(undefined, _dateMonth.value, _dateYear.value);
-               },
-               
-               /**
-                * Handles changes to the month select element.
-                * 
-                * @param       {object}        event           event object
-                */
-               _changeMonth: function(event) {
-                       this._renderGrid(undefined, event.currentTarget.value);
-               },
-               
-               /**
-                * Handles changes to the year select element.
-                * 
-                * @param       {object}        event           event object
-                */
-               _changeYear: function(event) {
-                       this._renderGrid(undefined, undefined, event.currentTarget.value);
-               },
-               
-               /**
-                * Handles clicks on an individual day.
-                * 
-                * @param       {object}        event           event object
-                */
-               _click: function(event) {
-                       event.preventDefault();
-                       
-                       if (event.currentTarget.classList.contains('otherMonth')) {
-                               return;
-                       }
-                       
-                       elData(_input, 'empty', false);
-                       
-                       this._renderGrid(event.currentTarget.textContent);
-                       
-                       var data = _data.get(_input);
-                       if (!data.isDateTime) {
-                               this._close();
-                       }
-               },
-               
-               /**
-                * Returns the current Date object or null.
-                * 
-                * @param       {(Element|string)}      element         input element or id
-                * @return      {?Date}                 Date object or null
-                */
-               getDate: function(element) {
-                       element = this._getElement(element);
-                       
-                       if (element.hasAttribute('data-value')) {
-                               return new Date(+elData(element, 'value'));
-                       }
-                       
-                       return null;
-               },
-               
-               /**
-                * Sets the date of given element.
-                * 
-                * @param       {(HTMLInputElement|string)}     element         input element or id
-                * @param       {Date}                          date            Date object
-                */
-               setDate: function(element, date) {
-                       element = this._getElement(element);
-                       var data = _data.get(element);
-                       
-                       elData(element, 'value', date.getTime());
-
-                       var format = '', value;
-                       if (data.isDateTime) {
-                               if (data.isTimeOnly) {
-                                       value = DateUtil.formatTime(date);
-                                       format = 'H:i';
-                               }
-                               else if (data.ignoreTimezone) {
-                                       value = DateUtil.formatDateTime(date);
-                                       format = 'Y-m-dTH:i:s';
-                               }
-                               else {
-                                       value = DateUtil.formatDateTime(date);
-                                       format = 'c';
-                               }
-                       }
-                       else {
-                               value = DateUtil.formatDate(date);
-                               format = 'Y-m-d';
-                       }
-
-                       element.value = value;
-                       data.shadow.value = DateUtil.format(date, format);
-
-                       // show clear button
-                       if (!data.disableClear) {
-                               data.clearButton.style.removeProperty('visibility');
-                       }
-               },
-               
-               /**
-                * Returns the current value.
-                * 
-                * @param       {(Element|string)}      element         input element or id
-                * @return      {string}                current date value
-                */
-               getValue: function (element) {
-                       element = this._getElement(element);
-                       var data = _data.get(element);
-                       
-                       if (data) {
-                               return data.shadow.value;
-                       }
-                       
-                       return '';
-               },
-               
-               /**
-                * Clears the date value of given element.
-                * 
-                * @param       {(HTMLInputElement|string)}     element         input element or id
-                */
-               clear: function(element) {
-                       element = this._getElement(element);
-                       var data = _data.get(element);
-                       
-                       element.removeAttribute('data-value');
-                       element.value = '';
-                       
-                       if (!data.disableClear) data.clearButton.style.setProperty('visibility', 'hidden', '');
-                       data.isEmpty = true;
-                       data.shadow.value = '';
-               },
-               
-               /**
-                * Reverts the date picker into a normal input field.
-                * 
-                * @param       {(HTMLInputElement|string)}     element         input element or id
-                */
-               destroy: function(element) {
-                       element = this._getElement(element);
-                       var data = _data.get(element);
-                       
-                       var container = element.parentNode;
-                       container.parentNode.insertBefore(element, container);
-                       elRemove(container);
-                       
-                       elAttr(element, 'type', 'date' + (data.isDateTime ? 'time' : ''));
-                       element.name = data.shadow.name;
-                       element.value = data.shadow.value;
-                       
-                       element.removeAttribute('data-value');
-                       element.removeEventListener(WCF_CLICK_EVENT, _callbackOpen);
-                       elRemove(data.shadow);
-                       
-                       element.classList.remove('inputDatePicker');
-                       element.readOnly = false;
-                       _data['delete'](element);
-               },
-               
-               /**
-                * Sets the callback invoked on picker close.
-                * 
-                * @param       {(Element|string)}      element         input element or id
-                * @param       {function}              callback        callback function
-                */
-               setCloseCallback: function(element, callback) {
-                       element = this._getElement(element);
-                       _data.get(element).onClose = callback;
-               },
-               
-               /**
-                * Validates given element or id if it represents an active date picker.
-                * 
-                * @param       {(Element|string)}      element         input element or id
-                * @return      {Element}               input element
-                */
-               _getElement: function(element) {
-                       if (typeof element === 'string') element = elById(element);
-                       
-                       if (!(element instanceof Element) || !element.classList.contains('inputDatePicker') || !_data.has(element)) {
-                               throw new Error("Expected a valid date picker input element or id.");
-                       }
-                       
-                       return element;
-               },
-               
-               /**
-                * @param {Event} event
-                */
-               _maintainFocus: function(event) {
-                       if (_datePicker !== null && _datePicker.classList.contains('active')) {
-                               if (!_datePicker.contains(event.target)) {
-                                       if (_wasInsidePicker) {
-                                               _input.nextElementSibling.focus();
-                                               _wasInsidePicker = false;
-                                       }
-                                       else {
-                                               elBySel('.previous', _datePicker).focus();
-                                       }
-                               }
-                               else {
-                                       _wasInsidePicker = true;
-                               }
-                       }
-               }
-       };
-       
-       // backward-compatibility for `$.ui.datepicker` shim
-       window.__wcf_bc_datePicker = DatePicker;
-       
-       return DatePicker;
-});
-
-/**
- * Provides page actions such as "jump to top" and clipboard actions.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Page/Action
- */
-define('WoltLabSuite/Core/Ui/Page/Action',['Dictionary', 'Dom/Util'], function(Dictionary, DomUtil) {
-       "use strict";
-       
-       var _buttons = new Dictionary();
-       var _container = null;
-       var _didInit = false;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Page/Action
-        */
-       return {
-               /**
-                * Initializes the page action container.
-                */
-               setup: function() {
-                       _didInit = true;
-                       
-                       _container = elCreate('ul');
-                       _container.className = 'pageAction';
-                       document.body.appendChild(_container);
-               },
-               
-               /**
-                * Adds a button to the page action list. You can optionally provide a button name to
-                * insert the button right before it. Unmatched button names or empty value will cause
-                * the button to be prepended to the list.
-                * 
-                * @param       {string}        buttonName              unique identifier
-                * @param       {Element}       button                  button element, must not be wrapped in a <li>
-                * @param       {string=}       insertBeforeButton      insert button before element identified by provided button name
-                */
-               add: function(buttonName, button, insertBeforeButton) {
-                       if (_didInit === false) this.setup();
-                       
-                       var listItem = elCreate('li');
-                       button.classList.add('button');
-                       button.classList.add('buttonPrimary');
-                       listItem.appendChild(button);
-                       elAttr(listItem, 'aria-hidden', (buttonName === 'toTop' ? 'true' : 'false'));
-                       elData(listItem, 'name', buttonName);
-                       
-                       // force 'to top' button to be always at the most outer position
-                       if (buttonName === 'toTop') {
-                               listItem.className = 'toTop initiallyHidden';
-                               _container.appendChild(listItem);
-                       }
-                       else {
-                               var insertBefore = null;
-                               if (insertBeforeButton) {
-                                       insertBefore = _buttons.get(insertBeforeButton);
-                                       if (insertBefore !== undefined) {
-                                               insertBefore = insertBefore.parentNode;
-                                       }
-                               }
-                               
-                               if (insertBefore === null && _container.childElementCount) {
-                                       insertBefore = _container.children[0];
-                               }
-                               
-                               if (insertBefore === null) {
-                                       DomUtil.prepend(listItem, _container);
-                               }
-                               else {
-                                       _container.insertBefore(listItem, insertBefore);
-                               }
-                       }
-                       
-                       _buttons.set(buttonName, button);
-                       this._renderContainer();
-               },
-               
-               /**
-                * Returns true if there is a registered button with the provided name.
-                * 
-                * @param       {string}        buttonName      unique identifier
-                * @return      {boolean}       true if there is a registered button with this name
-                */
-               has: function (buttonName) {
-                       return _buttons.has(buttonName);
-               },
-               
-               /**
-                * Returns the stored button by name or undefined.
-                * 
-                * @param       {string}        buttonName      unique identifier
-                * @return      {Element}       button element or undefined
-                */
-               get: function(buttonName) {
-                       return _buttons.get(buttonName);
-               },
-               
-               /**
-                * Removes a button by its button name.
-                * 
-                * @param       {string}        buttonName      unique identifier
-                */
-               remove: function(buttonName) {
-                       var button = _buttons.get(buttonName);
-                       if (button !== undefined) {
-                               var listItem = button.parentNode;
-                               listItem.addEventListener('animationend', function () {
-                                       try {
-                                               _container.removeChild(listItem);
-                                               _buttons.delete(buttonName);
-                                       }
-                                       catch (e) {
-                                               // ignore errors if the element has already been removed
-                                       }
-                               });
-                               
-                               this.hide(buttonName);
-                       }
-               },
-               
-               /**
-                * Hides a button by its button name.
-                * 
-                * @param       {string}        buttonName      unique identifier
-                */
-               hide: function(buttonName) {
-                       var button = _buttons.get(buttonName);
-                       if (button) {
-                               elAttr(button.parentNode, 'aria-hidden', 'true');
-                               this._renderContainer();
-                       }
-               },
-               
-               /**
-                * Shows a button by its button name.
-                * 
-                * @param       {string}        buttonName      unique identifier
-                */
-               show: function(buttonName) {
-                       var button = _buttons.get(buttonName);
-                       if (button) {
-                               if (button.parentNode.classList.contains('initiallyHidden')) {
-                                       button.parentNode.classList.remove('initiallyHidden');
-                               }
-                               
-                               elAttr(button.parentNode, 'aria-hidden', 'false');
-                               this._renderContainer();
-                       }
-               },
-               
-               /**
-                * Toggles the container's visibility.
-                * 
-                * @protected
-                */
-               _renderContainer: function() {
-                       var hasVisibleItems = false;
-                       if (_container.childElementCount) {
-                               for (var i = 0, length = _container.childElementCount; i < length; i++) {
-                                       if (elAttr(_container.children[i], 'aria-hidden') === 'false') {
-                                               hasVisibleItems = true;
-                                               break;
-                                       }
-                               }
-                       }
-                       
-                       _container.classList[(hasVisibleItems ? 'add' : 'remove')]('active');
-               }
-       };
-});
-
-/**
- * Provides a link to scroll to top once the page is scrolled by at least 50% the height of the window.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Page/JumpToTop
- */
-define('WoltLabSuite/Core/Ui/Page/JumpToTop',['Environment', 'Language', './Action'], function(Environment, Language, PageAction) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function JumpToTop() { this.init(); }
-       JumpToTop.prototype = {
-               /**
-                * Initializes the top link for desktop browsers only.
-                */
-               init: function() {
-                       // top link is not available on smartphones and tablets (they have a built-in function to accomplish this)
-                       if (Environment.platform() !== 'desktop') {
-                               return;
-                       }
-                       
-                       this._callbackScrollEnd = this._afterScroll.bind(this);
-                       this._timeoutScroll = null;
-                       
-                       var button = elCreate('a');
-                       button.className = 'jsTooltip';
-                       button.href = '#';
-                       elAttr(button, 'title', Language.get('wcf.global.scrollUp'));
-                       elAttr(button, 'role', 'button');
-                       button.innerHTML = '<span class="icon icon32 fa-angle-up"></span>';
-                       
-                       button.addEventListener(WCF_CLICK_EVENT, this._jump.bind(this));
-                       
-                       PageAction.add('toTop', button);
-                       
-                       window.addEventListener('scroll', this._scroll.bind(this));
-                       
-                       // invoke callback on page load
-                       this._afterScroll();
-               },
-               
-               /**
-                * Handles clicks on the top link.
-                * 
-                * @param       {Event}         event   event object
-                * @protected
-                */
-               _jump: function(event) {
-                       event.preventDefault();
-                       
-                       elById('top').scrollIntoView({ behavior: 'smooth' });
-               },
-               
-               /**
-                * Callback executed whenever the window is being scrolled.
-                * 
-                * @protected
-                */
-               _scroll: function() {
-                       if (this._timeoutScroll !== null) {
-                               window.clearTimeout(this._timeoutScroll);
-                       }
-                       
-                       this._timeoutScroll = window.setTimeout(this._callbackScrollEnd, 100);
-               },
-               
-               /**
-                * Delayed callback executed once the page has not been scrolled for a certain amount of time.
-                * 
-                * @protected
-                */
-               _afterScroll: function() {
-                       this._timeoutScroll = null;
-                       
-                       PageAction[(window.pageYOffset >= 300) ? 'show' : 'hide']('toTop');
-               }
-       };
-       
-       return JumpToTop;
-});
-
-/**
- * Bootstraps WCF's JavaScript.
- * It defines globals needed for backwards compatibility
- * and runs modules that are needed on page load.
- * 
- * @author     Tim Duesterhus
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Bootstrap
- */
-define(
-       'WoltLabSuite/Core/Bootstrap',[
-               'favico',                  'enquire',                'perfect-scrollbar',      'WoltLabSuite/Core/Date/Time/Relative',
-               'Ui/SimpleDropdown',       'WoltLabSuite/Core/Ui/Mobile',  'WoltLabSuite/Core/Ui/TabMenu', 'WoltLabSuite/Core/Ui/FlexibleMenu',
-               'Ui/Dialog',               'WoltLabSuite/Core/Ui/Tooltip', 'WoltLabSuite/Core/Language',   'WoltLabSuite/Core/Environment',
-               'WoltLabSuite/Core/Date/Picker', 'EventHandler',           'Core',                   'WoltLabSuite/Core/Ui/Page/JumpToTop',
-               'Devtools', 'Dom/ChangeListener'
-       ], 
-       function(
-                favico,                   enquire,                  perfectScrollbar,         DateTimeRelative,
-                UiSimpleDropdown,         UiMobile,                 UiTabMenu,                UiFlexibleMenu,
-                UiDialog,                 UiTooltip,                Language,                 Environment,
-                DatePicker,               EventHandler,             Core,                     UiPageJumpToTop,
-                Devtools, DomChangeListener
-       )
-{
-       "use strict";
-       
-       // perfectScrollbar does not need to be bound anywhere, it just has to be loaded for WCF.js
-       window.Favico = favico;
-       window.enquire = enquire;
-       // non strict equals by intent
-       if (window.WCF == null) window.WCF = { };
-       if (window.WCF.Language == null) window.WCF.Language = { };
-       window.WCF.Language.get = Language.get;
-       window.WCF.Language.add = Language.add;
-       window.WCF.Language.addObject = Language.addObject;
-       
-       // WCF.System.Event compatibility
-       window.__wcf_bc_eventHandler = EventHandler;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Bootstrap
-        */
-       return {
-               /**
-                * Initializes the core UI modifications and unblocks jQuery's ready event.
-                * 
-                * @param       {Object=}       options         initialization options
-                */
-               setup: function(options) {
-                       options = Core.extend({
-                               enableMobileMenu: true
-                       }, options);
-                       
-                       //noinspection JSUnresolvedVariable
-                       if (window.ENABLE_DEVELOPER_TOOLS) Devtools._internal_.enable();
-                       
-                       Environment.setup();
-                       
-                       DateTimeRelative.setup();
-                       DatePicker.init();
-                       
-                       UiSimpleDropdown.setup();
-                       UiMobile.setup({
-                               enableMobileMenu: options.enableMobileMenu
-                       });
-                       UiTabMenu.setup();
-                       //UiFlexibleMenu.setup();
-                       UiDialog.setup();
-                       UiTooltip.setup();
-                       
-                       // convert method=get into method=post
-                       var forms = elBySelAll('form[method=get]');
-                       for (var i = 0, length = forms.length; i < length; i++) {
-                               forms[i].setAttribute('method', 'post');
-                       }
-                       
-                       if (Environment.browser() === 'microsoft') {
-                               window.onbeforeunload = function() {
-                                       /* Prevent "Back navigation caching" (http://msdn.microsoft.com/en-us/library/ie/dn265017%28v=vs.85%29.aspx) */
-                               };
-                       }
-                       
-                       var interval = 0;
-                       interval = window.setInterval(function() {
-                               if (typeof window.jQuery === 'function') {
-                                       window.clearInterval(interval);
-                                       
-                                       // the 'jump to top' button triggers style recalculation/layout,
-                                       // putting it at the end of the jQuery queue avoids trashing the
-                                       // layout too early and thus delaying the page initialization
-                                       window.jQuery(function() {
-                                               new UiPageJumpToTop();
-                                       });
-                                       
-                                       window.jQuery.holdReady(false);
-                               }
-                       }, 20);
-                       
-                       this._initA11y();
-                       DomChangeListener.add('WoltLabSuite/Core/Bootstrap', this._initA11y.bind(this));
-               },
-               
-               _initA11y: function() {
-                       elBySelAll('nav:not([aria-label]):not([aria-labelledby]):not([role])', undefined, function(element) {
-                               elAttr(element, 'role', 'presentation');
-                       });
-                       
-                       elBySelAll('article:not([aria-label]):not([aria-labelledby]):not([role])', undefined, function(element) {
-                               elAttr(element, 'role', 'presentation');
-                       });
-               }
-       };
-});
-
-/**
- * Dialog based style changer.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Controller/Style/Changer
- */
-define('WoltLabSuite/Core/Controller/Style/Changer',['Ajax', 'Language', 'Ui/Dialog'], function(Ajax, Language, UiDialog) {
-       "use strict";
-       
-       /**
-        * @exports     WoltLabSuite/Core/Controller/Style/Changer
-        */
-       return {
-               /**
-                * Adds the style changer to the bottom navigation.
-                */
-               setup: function() {
-                       var link = elBySel('.jsButtonStyleChanger');
-                       if (link) {
-                               link.addEventListener(WCF_CLICK_EVENT, this.showDialog.bind(this));
-                       }
-               },
-               
-               /**
-                * Loads and displays the style change dialog.
-                * 
-                * @param       {object}        event   event object
-                */
-               showDialog: function(event) {
-                       event.preventDefault();
-                       
-                       UiDialog.open(this);
-               },
-               
-               _dialogSetup: function() {
-                       return {
-                               id: 'styleChanger',
-                               options: {
-                                       disableContentPadding: true,
-                                       title: Language.get('wcf.style.changeStyle')
-                               },
-                               source: {
-                                       data: {
-                                               actionName: 'getStyleChooser',
-                                               className: 'wcf\\data\\style\\StyleAction'
-                                       },
-                                       after: (function(content) {
-                                               var styles = elBySelAll('.styleList > li', content);
-                                               for (var i = 0, length = styles.length; i < length; i++) {
-                                                       var style = styles[i];
-                                                       
-                                                       style.classList.add('pointer');
-                                                       style.addEventListener(WCF_CLICK_EVENT, this._click.bind(this));
-                                               }
-                                       }).bind(this)
-                               }
-                       };
-               },
-               
-               /**
-                * Changes the style and reloads current page.
-                * 
-                * @param       {object}        event   event object
-                */
-               _click: function(event) {
-                       event.preventDefault();
-                       
-                       Ajax.apiOnce({
-                               data: {
-                                       actionName: 'changeStyle',
-                                       className: 'wcf\\data\\style\\StyleAction',
-                                       objectIDs: [ elData(event.currentTarget, 'style-id') ]
-                               },
-                               success: function() { window.location.reload(); }
-                       });
-               }
-       };
-});
-
-/**
- * Versatile popover manager.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Controller/Popover
- */
-define('WoltLabSuite/Core/Controller/Popover',['Ajax', 'Dictionary', 'Environment', 'Dom/ChangeListener', 'Dom/Util', 'Ui/Alignment'], function(Ajax, Dictionary, Environment, DomChangeListener, DomUtil, UiAlignment) {
-       "use strict";
-       
-       var _activeId = null;
-       var _cache = new Dictionary();
-       var _elements = new Dictionary();
-       var _handlers = new Dictionary();
-       var _hoverId = null;
-       var _suspended = false;
-       var _timeoutEnter = null;
-       var _timeoutLeave = null;
-       
-       var _popover = null;
-       var _popoverContent = null;
-       
-       var _callbackClick = null;
-       var _callbackHide = null;
-       var _callbackMouseEnter = null;
-       var _callbackMouseLeave = null;
-       
-       /** @const */ var STATE_NONE = 0;
-       /** @const */ var STATE_LOADING = 1;
-       /** @const */ var STATE_READY = 2;
-       
-       /** @const */ var DELAY_HIDE = 500;
-       /** @const */ var DELAY_SHOW = 800;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Controller/Popover
-        */
-       return {
-               /**
-                * Builds popover DOM elements and binds event listeners.
-                */
-               _setup: function() {
-                       if (_popover !== null) {
-                               return;
-                       }
-                       
-                       _popover = elCreate('div');
-                       _popover.className = 'popover forceHide';
-                       
-                       _popoverContent = elCreate('div');
-                       _popoverContent.className = 'popoverContent';
-                       _popover.appendChild(_popoverContent);
-                       
-                       var pointer = elCreate('span');
-                       pointer.className = 'elementPointer';
-                       pointer.appendChild(elCreate('span'));
-                       _popover.appendChild(pointer);
-                       
-                       document.body.appendChild(_popover);
-                       
-                       // static binding for callbacks (they don't change anyway and binding each time is expensive)
-                       _callbackClick = this._hide.bind(this);
-                       _callbackMouseEnter = this._mouseEnter.bind(this);
-                       _callbackMouseLeave = this._mouseLeave.bind(this);
-                       
-                       // event listener
-                       _popover.addEventListener('mouseenter', this._popoverMouseEnter.bind(this));
-                       _popover.addEventListener('mouseleave', _callbackMouseLeave);
-                       
-                       _popover.addEventListener('animationend', this._clearContent.bind(this));
-                       
-                       window.addEventListener('beforeunload', (function() {
-                               _suspended = true;
-                               
-                               if (_timeoutEnter !== null) {
-                                       window.clearTimeout(_timeoutEnter);
-                               }
-                               
-                               this._hide(true);
-                       }).bind(this));
-                       
-                       DomChangeListener.add('WoltLabSuite/Core/Controller/Popover', this._init.bind(this));
-               },
-               
-               /**
-                * Initializes a popover handler.
-                * 
-                * Usage:
-                * 
-                * ControllerPopover.init({
-                *      attributeName: 'data-object-id',
-                *      className: 'fooLink',
-                *      identifier: 'com.example.bar.foo',
-                *      loadCallback: function(objectId, popover) {
-                *              // request data for object id (e.g. via WoltLabSuite/Core/Ajax)
-                *              
-                *              // then call this to set the content
-                *              popover.setContent('com.example.bar.foo', objectId, htmlTemplateString);
-                *      }
-                * });
-                * 
-                * @param       {Object}        options         handler options
-                */
-               init: function(options) {
-                       if (Environment.platform() !== 'desktop') {
-                               return;
-                       }
-                       
-                       options.attributeName = options.attributeName || 'data-object-id';
-                       options.legacy = (options.legacy === true);
-                       
-                       this._setup();
-                       
-                       if (_handlers.has(options.identifier)) {
-                               return;
-                       }
-                       
-                       _handlers.set(options.identifier, {
-                               attributeName: options.attributeName,
-                               elements: options.legacy ? options.className : elByClass(options.className),
-                               legacy: options.legacy,
-                               loadCallback: options.loadCallback
-                       });
-                       
-                       this._init(options.identifier);
-               },
-               
-               /**
-                * Initializes a popover handler.
-                * 
-                * @param       {string}        identifier      handler identifier
-                */
-               _init: function(identifier) {
-                       if (typeof identifier === 'string' && identifier.length) {
-                               this._initElements(_handlers.get(identifier), identifier);
-                       }
-                       else {
-                               _handlers.forEach(this._initElements.bind(this));
-                       }
-               },
-               
-               /**
-                * Binds event listeners for popover-enabled elements.
-                * 
-                * @param       {Object}        options         handler options
-                * @param       {string}        identifier      handler identifier
-                */
-               _initElements: function(options, identifier) {
-                       var elements = options.legacy ? elBySelAll(options.elements) : options.elements;
-                       for (var i = 0, length = elements.length; i < length; i++) {
-                               var element = elements[i];
-                               
-                               var id = DomUtil.identify(element);
-                               if (_cache.has(id)) {
-                                       return;
-                               }
-                               // skip if element is in a popover
-                               if (element.closest('.popover') !== null) {
-                                       _cache.set(id, {
-                                               content: null,
-                                               state: STATE_NONE
-                                       });
-                                       return;
-                               }
-                               
-                               var objectId = (options.legacy) ? id : ~~element.getAttribute(options.attributeName);
-                               if (objectId === 0) {
-                                       continue;
-                               }
-                               
-                               element.addEventListener('mouseenter', _callbackMouseEnter);
-                               element.addEventListener('mouseleave', _callbackMouseLeave);
-                               
-                               if (element.nodeName === 'A' && elAttr(element, 'href')) {
-                                       element.addEventListener(WCF_CLICK_EVENT, _callbackClick);
-                               }
-                               
-                               var cacheId = identifier + "-" + objectId;
-                               elData(element, 'cache-id', cacheId);
-                               
-                               _elements.set(id, {
-                                       element: element,
-                                       identifier: identifier,
-                                       objectId: objectId
-                               });
-                               
-                               if (!_cache.has(cacheId)) {
-                                       _cache.set(identifier + "-" + objectId, {
-                                               content: null,
-                                               state: STATE_NONE
-                                       });
-                               }
-                       }
-               },
-               
-               /**
-                * Sets the content for given identifier and object id.
-                * 
-                * @param       {string}        identifier      handler identifier
-                * @param       {int}           objectId        object id
-                * @param       {string}        content         HTML string
-                */
-               setContent: function(identifier, objectId, content) {
-                       var cacheId = identifier + "-" + objectId;
-                       var data = _cache.get(cacheId);
-                       if (data === undefined) {
-                               throw new Error("Unable to find element for object id '" + objectId + "' (identifier: '" + identifier + "').");
-                       }
-                       
-                       var fragment = DomUtil.createFragmentFromHtml(content);
-                       if (!fragment.childElementCount) fragment = DomUtil.createFragmentFromHtml('<p>' + content + '</p>');
-                       data.content = fragment;
-                       data.state = STATE_READY;
-                       
-                       if (_activeId) {
-                               var activeElement = _elements.get(_activeId).element;
-                               
-                               if (elData(activeElement, 'cache-id') === cacheId) {
-                                       this._show();
-                               }
-                       }
-               },
-               
-               /**
-                * Handles the mouse start hovering the popover-enabled element.
-                * 
-                * @param       {object}        event   event object
-                */
-               _mouseEnter: function(event) {
-                       if (_suspended) {
-                               return;
-                       }
-                       
-                       if (_timeoutEnter !== null) {
-                               window.clearTimeout(_timeoutEnter);
-                               _timeoutEnter = null;
-                       }
-                       
-                       var id = DomUtil.identify(event.currentTarget);
-                       if (_activeId === id && _timeoutLeave !== null) {
-                               window.clearTimeout(_timeoutLeave);
-                               _timeoutLeave = null;
-                       }
-                       
-                       _hoverId = id;
-                       
-                       _timeoutEnter = window.setTimeout((function() {
-                               _timeoutEnter = null;
-                               
-                               if (_hoverId === id) {
-                                       this._show();
-                               }
-                       }).bind(this), DELAY_SHOW);
-               },
-               
-               /**
-                * Handles the mouse leaving the popover-enabled element or the popover itself.
-                */
-               _mouseLeave: function() {
-                       _hoverId = null;
-                       
-                       if (_timeoutLeave !== null) {
-                               return;
-                       }
-                       
-                       if (_callbackHide === null) {
-                               _callbackHide = this._hide.bind(this);
-                       }
-                       
-                       if (_timeoutLeave !== null) {
-                               window.clearTimeout(_timeoutLeave);
-                       }
-                       
-                       _timeoutLeave = window.setTimeout(_callbackHide, DELAY_HIDE);
-               },
-               
-               /**
-                * Handles the mouse start hovering the popover element.
-                */
-               _popoverMouseEnter: function() {
-                       if (_timeoutLeave !== null) {
-                               window.clearTimeout(_timeoutLeave);
-                               _timeoutLeave = null;
-                       }
-               },
-               
-               /**
-                * Shows the popover and loads content on-the-fly.
-                */
-               _show: function() {
-                       if (_timeoutLeave !== null) {
-                               window.clearTimeout(_timeoutLeave);
-                               _timeoutLeave = null;
-                       }
-                       
-                       var forceHide = false;
-                       if (_popover.classList.contains('active')) {
-                               if (_activeId !== _hoverId) {
-                                       this._hide();
-                                       
-                                       forceHide = true;
-                               }
-                       }
-                       else if (_popoverContent.childElementCount) {
-                               forceHide = true;
-                       }
-                       
-                       if (forceHide) {
-                               _popover.classList.add('forceHide');
-                               
-                               // force layout
-                               //noinspection BadExpressionStatementJS
-                               _popover.offsetTop;
-                               
-                               this._clearContent();
-                               
-                               _popover.classList.remove('forceHide');
-                       }
-                       
-                       _activeId = _hoverId;
-                       
-                       var elementData = _elements.get(_activeId);
-                       // check if source element is already gone
-                       if (elementData === undefined) {
-                               return;
-                       }
-                       
-                       var data = _cache.get(elData(elementData.element, 'cache-id'));
-                       
-                       if (data.state === STATE_READY) {
-                               _popoverContent.appendChild(data.content);
-                               
-                               this._rebuild(_activeId);
-                       }
-                       else if (data.state === STATE_NONE) {
-                               data.state = STATE_LOADING;
-                               
-                               _handlers.get(elementData.identifier).loadCallback(elementData.objectId, this);
-                       }
-               },
-               
-               /**
-                * Hides the popover element.
-                */
-               _hide: function() {
-                       if (_timeoutLeave !== null) {
-                               window.clearTimeout(_timeoutLeave);
-                               _timeoutLeave = null;
-                       }
-                       
-                       _popover.classList.remove('active');
-               },
-               
-               /**
-                * Clears popover content by moving it back into the cache.
-                */
-               _clearContent: function() {
-                       if (_activeId && _popoverContent.childElementCount && !_popover.classList.contains('active')) {
-                               var activeElData = _cache.get(elData(_elements.get(_activeId).element, 'cache-id'));
-                               while (_popoverContent.childNodes.length) {
-                                       activeElData.content.appendChild(_popoverContent.childNodes[0]);
-                               }
-                       }
-               },
-               
-               /**
-                * Rebuilds the popover.
-                */
-               _rebuild: function() {
-                       if (_popover.classList.contains('active')) {
-                               return;
-                       }
-                       
-                       _popover.classList.remove('forceHide');
-                       _popover.classList.add('active');
-                       
-                       UiAlignment.set(_popover, _elements.get(_activeId).element, {
-                               pointer: true,
-                               vertical: 'top'
-                       });
-               },
-               
-               _ajaxSetup: function() {
-                       return {
-                               silent: true
-                       };
-               },
-               
-               /**
-                * Sends an AJAX requests to the server, simple wrapper to reuse the request object.
-                * 
-                * @param       {Object}        data            request data
-                * @param       {function}      success         success callback
-                * @param       {function=}     failure         error callback
-                */
-               ajaxApi: function(data, success, failure) {
-                       if (typeof success !== 'function') {
-                               throw new TypeError("Expected a valid callback for parameter 'success'.");
-                       }
-                       
-                       Ajax.api(this, data, success, failure);
-               }
-       };
-});
-
-/**
- * Provides global helper methods to interact with ignored content.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/User/Ignore
- */
-define('WoltLabSuite/Core/Ui/User/Ignore',['List', 'Dom/ChangeListener'], function(List, DomChangeListener) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _rebuild: function() {},
-                       _removeClass: function() {}
-               };
-               return Fake;
-       }
-       
-       var _availableMessages = elByClass('ignoredUserMessage');
-       var _callback = null;
-       var _knownMessages = new List();
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/User/Ignore
-        */
-       return {
-               /**
-                * Initializes the click handler for each ignored message and listens for
-                * newly inserted messages.
-                */
-               init: function () {
-                       _callback = this._removeClass.bind(this);
-                       
-                       this._rebuild();
-                       
-                       DomChangeListener.add('WoltLabSuite/Core/Ui/User/Ignore', this._rebuild.bind(this));
-               },
-               
-               /**
-                * Adds ignored messages to the collection.
-                * 
-                * @protected
-                */
-               _rebuild: function() {
-                       var message;
-                       for (var i = 0, length = _availableMessages.length; i < length; i++) {
-                               message = _availableMessages[i];
-                               
-                               if (!_knownMessages.has(message)) {
-                                       message.addEventListener(WCF_CLICK_EVENT, _callback);
-                                       
-                                       _knownMessages.add(message);
-                               }
-                       }
-               },
-               
-               /**
-                * Reveals a message on click/tap and disables the listener.
-                * 
-                * @param       {Event}         event   event object
-                * @protected
-                */
-               _removeClass: function(event) {
-                       event.preventDefault();
-                       
-                       var message = event.currentTarget;
-                       message.classList.remove('ignoredUserMessage');
-                       message.removeEventListener(WCF_CLICK_EVENT, _callback);
-                       _knownMessages.delete(message);
-                       
-                       // Firefox selects the entire message on click for no reason
-                       window.getSelection().removeAllRanges();
-               }
-       };
-});
-
-/**
- * Handles main menu overflow and a11y.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Page/Header/Menu
- */
-define('WoltLabSuite/Core/Ui/Page/Header/Menu',['Environment', 'Language', 'Ui/Screen'], function(Environment, Language, UiScreen) {
-       "use strict";
-       
-       var _enabled = false;
-       
-       // elements
-       var _buttonShowNext, _buttonShowPrevious, _firstElement, _menu;
-       
-       // internal states
-       var _marginLeft = 0, _invisibleLeft = [], _invisibleRight = [];
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Page/Header/Menu
-        */
-       return {
-               /**
-                * Initializes the main menu overflow handling.
-                */
-               init: function () {
-                       _menu = elBySel('.mainMenu .boxMenu');
-                       _firstElement = (_menu && _menu.childElementCount) ? _menu.children[0] : null;
-                       if (_firstElement === null) {
-                               throw new Error("Unable to find the menu.");
-                       }
-                       
-                       UiScreen.on('screen-lg', {
-                               enable: this._enable.bind(this),
-                               disable: this._disable.bind(this),
-                               setup: this._setup.bind(this)
-                       });
-               },
-               
-               /**
-                * Enables the overflow handler.
-                * 
-                * @protected
-                */
-               _enable: function () {
-                       _enabled = true;
-                       
-                       // Safari waits three seconds for a font to be loaded which causes the header menu items
-                       // to be extremely wide while waiting for the font to be loaded. The extremely wide menu
-                       // items in turn can cause the overflow controls to be shown even if the width of the header
-                       // menu, after the font has been loaded successfully, does not require them. This width
-                       // issue results in the next button being shown for a short time. To circumvent this issue,
-                       // we wait a second before showing the obverflow controls in Safari.
-                       // see https://webkit.org/blog/6643/improved-font-loading/
-                       if (Environment.browser() === 'safari') {
-                               window.setTimeout(this._rebuildVisibility.bind(this), 1000);
-                       }
-                       else {
-                               this._rebuildVisibility();
-                               
-                               // IE11 sometimes suffers from a timing issue
-                               window.setTimeout(this._rebuildVisibility.bind(this), 1000);
-                       }
-               },
-               
-               /**
-                * Disables the overflow handler.
-                * 
-                * @protected
-                */
-               _disable: function () {
-                       _enabled = false;
-               },
-               
-               /**
-                * Displays the next three menu items.
-                * 
-                * @param       {Event}         event           event object
-                * @protected
-                */
-               _showNext: function(event) {
-                       event.preventDefault();
-                       
-                       if (_invisibleRight.length) {
-                               var showItem = _invisibleRight.slice(0, 3).pop();
-                               this._setMarginLeft(_menu.clientWidth - (showItem.offsetLeft + showItem.clientWidth));
-                               
-                               if (_menu.lastElementChild === showItem) {
-                                       _buttonShowNext.classList.remove('active');
-                               }
-                               
-                               _buttonShowPrevious.classList.add('active');
-                       }
-               },
-               
-               /**
-                * Displays the previous three menu items.
-                * 
-                * @param       {Event}         event           event object
-                * @protected
-                */
-               _showPrevious: function (event) {
-                       event.preventDefault();
-                       
-                       if (_invisibleLeft.length) {
-                               var showItem = _invisibleLeft.slice(-3)[0];
-                               this._setMarginLeft(showItem.offsetLeft * -1);
-                               
-                               if (_menu.firstElementChild === showItem) {
-                                       _buttonShowPrevious.classList.remove('active');
-                               }
-                               
-                               _buttonShowNext.classList.add('active');
-                       }
-               },
-               
-               /**
-                * Sets the first item's margin-left value that is
-                * used to move the menu contents around.
-                * 
-                * @param       {int}   offset  changes to the margin-left value in pixel
-                * @protected
-                */
-               _setMarginLeft: function (offset) {
-                       _marginLeft = Math.min(_marginLeft + offset, 0);
-                       
-                       _firstElement.style.setProperty('margin-left', _marginLeft + 'px', '');
-               },
-               
-               /**
-                * Toggles button overlays and rebuilds the list
-                * of invisible items from left to right.
-                * 
-                * @protected
-                */
-               _rebuildVisibility: function () {
-                       if (!_enabled) return;
-                       
-                       _invisibleLeft = [];
-                       _invisibleRight = [];
-                       
-                       var menuWidth = _menu.clientWidth;
-                       if (_menu.scrollWidth > menuWidth || _marginLeft < 0) {
-                               var child;
-                               for (var i = 0, length = _menu.childElementCount; i < length; i++) {
-                                       child = _menu.children[i];
-                                       
-                                       var offsetLeft = child.offsetLeft;
-                                       if (offsetLeft < 0) {
-                                               _invisibleLeft.push(child);
-                                       }
-                                       else if (offsetLeft + child.clientWidth > menuWidth) {
-                                               _invisibleRight.push(child);
-                                       }
-                               }
-                       }
-                       
-                       _buttonShowPrevious.classList[(_invisibleLeft.length ? 'add' : 'remove')]('active');
-                       _buttonShowNext.classList[(_invisibleRight.length ? 'add' : 'remove')]('active');
-               },
-               
-               /**
-                * Builds the UI and binds the event listeners.
-                *
-                * @protected
-                */
-               _setup: function () {
-                       this._setupOverflow();
-                       this._setupA11y();
-               },
-               
-               /**
-                * Setups overflow handling.
-                * 
-                * @protected
-                */
-               _setupOverflow: function () {
-                       _buttonShowNext = elCreate('a');
-                       _buttonShowNext.className = 'mainMenuShowNext';
-                       _buttonShowNext.href = '#';
-                       _buttonShowNext.innerHTML = '<span class="icon icon32 fa-angle-right"></span>';
-                       _buttonShowNext.addEventListener(WCF_CLICK_EVENT, this._showNext.bind(this));
-                       
-                       _menu.parentNode.appendChild(_buttonShowNext);
-                       
-                       _buttonShowPrevious = elCreate('a');
-                       _buttonShowPrevious.className = 'mainMenuShowPrevious';
-                       _buttonShowPrevious.href = '#';
-                       _buttonShowPrevious.innerHTML = '<span class="icon icon32 fa-angle-left"></span>';
-                       _buttonShowPrevious.addEventListener(WCF_CLICK_EVENT, this._showPrevious.bind(this));
-                       
-                       _menu.parentNode.insertBefore(_buttonShowPrevious, _menu.parentNode.firstChild);
-                       
-                       var rebuildVisibility = this._rebuildVisibility.bind(this);
-                       _firstElement.addEventListener('transitionend', rebuildVisibility);
-                       
-                       window.addEventListener('resize', function () {
-                               _firstElement.style.setProperty('margin-left', '0px', '');
-                               _marginLeft = 0;
-                               
-                               rebuildVisibility();
-                       });
-                       
-                       this._enable();
-               },
-               
-               /**
-                * Setups a11y improvements.
-                *
-                * @protected
-                */
-               _setupA11y: function() {
-                       elBySelAll('.boxMenuHasChildren', _menu, (function(element) {
-                               var showMenu = false;
-                               var link = elBySel('.boxMenuLink', element);
-                               if (link) {
-                                       elAttr(link, 'aria-haspopup', true);
-                                       elAttr(link, 'aria-expanded', showMenu);
-                               }
-                               
-                               var showMenuButton = elCreate('button');
-                               showMenuButton.className = 'visuallyHidden';
-                               showMenuButton.tabindex = 0;
-                               elAttr(showMenuButton, 'role', 'button');
-                               elAttr(showMenuButton, 'aria-label', Language.get('wcf.global.button.showMenu'));
-                               element.insertBefore(showMenuButton, link.nextSibling);
-                               
-                               showMenuButton.addEventListener(WCF_CLICK_EVENT, function() {
-                                       showMenu = !showMenu;
-                                       elAttr(link, 'aria-expanded', showMenu);
-                                       elAttr(showMenuButton, 'aria-label', (showMenu ? Language.get('wcf.global.button.hideMenu') : Language.get('wcf.global.button.showMenu')));
-                               });
-                       }).bind(this));
-               }
-       };
-});
-
-/**
- * Bootstraps WCF's JavaScript with additions for the frontend usage.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/BootstrapFrontend
- */
-define(
-       'WoltLabSuite/Core/BootstrapFrontend',[
-               'WoltLabSuite/Core/BackgroundQueue', 'WoltLabSuite/Core/Bootstrap', 'WoltLabSuite/Core/Controller/Style/Changer',
-               'WoltLabSuite/Core/Controller/Popover', 'WoltLabSuite/Core/Ui/User/Ignore', 'WoltLabSuite/Core/Ui/Page/Header/Menu'
-       ],
-       function(
-               BackgroundQueue, Bootstrap, ControllerStyleChanger,
-               ControllerPopover, UiUserIgnore, UiPageHeaderMenu
-       )
-{
-       "use strict";
-       
-       /**
-        * @exports     WoltLabSuite/Core/BootstrapFrontend
-        */
-       return {
-               /**
-                * Bootstraps general modules and frontend exclusive ones.
-                * 
-                * @param       {object<string, *>}     options         bootstrap options
-                */
-               setup: function(options) {
-                       // fix the background queue URL to always run against the current domain (avoiding CORS)
-                       options.backgroundQueue.url = WSC_API_URL + options.backgroundQueue.url.substr(WCF_PATH.length);
-                       
-                       Bootstrap.setup();
-                       
-                       UiPageHeaderMenu.init();
-                       
-                       if (options.styleChanger) {
-                               ControllerStyleChanger.setup();
-                       }
-                       
-                       if (options.enableUserPopover) {
-                               this._initUserPopover();
-                       }
-                       
-                       BackgroundQueue.setUrl(options.backgroundQueue.url);
-                       if (Math.random() < 0.1 || options.backgroundQueue.force) {
-                               // invoke the queue roughly every 10th request or on demand
-                               BackgroundQueue.invoke();
-                       }
-                       
-                       if (COMPILER_TARGET_DEFAULT) {
-                               UiUserIgnore.init();
-                       }
-               },
-               
-               /**
-                * Initializes user profile popover.
-                */
-               _initUserPopover: function() {
-                       ControllerPopover.init({
-                               attributeName: 'data-user-id',
-                               className: 'userLink',
-                               identifier: 'com.woltlab.wcf.user',
-                               loadCallback: function(objectId, popover) {
-                                       var callback = function(data) {
-                                               popover.setContent('com.woltlab.wcf.user', objectId, data.returnValues.template);
-                                       };
-                                       
-                                       popover.ajaxApi({
-                                               actionName: 'getUserProfile',
-                                               className: 'wcf\\data\\user\\UserProfileAction',
-                                               objectIDs: [ objectId ]
-                                       }, callback, callback);
-                               }
-                       });
-               }
-       };
-});
-
-/**
- * Wrapper around the web browser's various clipboard APIs.
- * 
- * @author     Tim Duesterhus
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Clipboard
- */
-define('WoltLabSuite/Core/Clipboard',[], function() {
-       "use strict";
-       
-       return {
-               copyTextToClipboard: function (text) {
-                       if (navigator.clipboard) {
-                               return navigator.clipboard.writeText(text);
-                       }
-                       else if (window.getSelection) {
-                               var textarea = elCreate('textarea');
-                               textarea.contentEditable = true;
-                               textarea.readOnly = false;
-                               textarea.style.cssText = 'position: absolute; left: -9999px; top: -9999px; width: 0; height: 0;';
-                               document.body.appendChild(textarea);
-                               try {
-                                       // see: https://stackoverflow.com/a/34046084/782822
-                                       textarea.value = text;
-                                       var range = document.createRange();
-                                       range.selectNodeContents(textarea);
-                                       var selection = window.getSelection();
-                                       selection.removeAllRanges();
-                                       selection.addRange(range);
-                                       textarea.setSelectionRange(0, 999999);
-                                       if (!document.execCommand('copy')) {
-                                               return Promise.reject(new Error("execCommand('copy') failed"));
-                                       }
-                                       return Promise.resolve();
-                               }
-                               finally {
-                                       elRemove(textarea);
-                               }
-                       }
-                       
-                       return Promise.reject(new Error('Neither navigator.clipboard, nor window.getSelection is supported.'));
-               },
-               
-               copyElementTextToClipboard: function (element) {
-                       return this.copyTextToClipboard(element.textContent);
-               }
-       };
-});
-
-/**
- * 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      WoltLabSuite/Core/ColorUtil
- */
-define('WoltLabSuite/Core/ColorUtil',[], 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;
-});
-
-/**
- * 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
- */
-define('WoltLabSuite/Core/FileUtil',['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});
-               },
-       };
-});
-
-/**
- * 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     WoltLabSuite/Core/Permission
- */
-define('WoltLabSuite/Core/Permission',['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;
-               }
-       };
-});
-
-/* PrismJS 1.15.0
-https://prismjs.com/download.html#themes=prism */
-var _self = (typeof window !== 'undefined')
-       ? window   // if in browser
-       : (
-               (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope)
-               ? self // if in worker
-               : {}   // if in node js
-       );
-
-/**
- * Prism: Lightweight, robust, elegant syntax highlighting
- * MIT license http://www.opensource.org/licenses/mit-license.php/
- * @author Lea Verou http://lea.verou.me
- */
-
-var Prism = (function(){
-
-// Private helper vars
-var lang = /\blang(?:uage)?-([\w-]+)\b/i;
-var uniqueId = 0;
-
-var _ = _self.Prism = {
-       manual: _self.Prism && _self.Prism.manual,
-       disableWorkerMessageHandler: _self.Prism && _self.Prism.disableWorkerMessageHandler,
-       util: {
-               encode: function (tokens) {
-                       if (tokens instanceof Token) {
-                               return new Token(tokens.type, _.util.encode(tokens.content), tokens.alias);
-                       } else if (_.util.type(tokens) === 'Array') {
-                               return tokens.map(_.util.encode);
-                       } else {
-                               return tokens.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/\u00a0/g, ' ');
-                       }
-               },
-
-               type: function (o) {
-                       return Object.prototype.toString.call(o).match(/\[object (\w+)\]/)[1];
-               },
-
-               objId: function (obj) {
-                       if (!obj['__id']) {
-                               Object.defineProperty(obj, '__id', { value: ++uniqueId });
-                       }
-                       return obj['__id'];
-               },
-
-               // Deep clone a language definition (e.g. to extend it)
-               clone: function (o, visited) {
-                       var type = _.util.type(o);
-                       visited = visited || {};
-
-                       switch (type) {
-                               case 'Object':
-                                       if (visited[_.util.objId(o)]) {
-                                               return visited[_.util.objId(o)];
-                                       }
-                                       var clone = {};
-                                       visited[_.util.objId(o)] = clone;
-
-                                       for (var key in o) {
-                                               if (o.hasOwnProperty(key)) {
-                                                       clone[key] = _.util.clone(o[key], visited);
-                                               }
-                                       }
-
-                                       return clone;
-
-                               case 'Array':
-                                       if (visited[_.util.objId(o)]) {
-                                               return visited[_.util.objId(o)];
-                                       }
-                                       var clone = [];
-                                       visited[_.util.objId(o)] = clone;
-
-                                       o.forEach(function (v, i) {
-                                               clone[i] = _.util.clone(v, visited);
-                                       });
-
-                                       return clone;
-                       }
-
-                       return o;
-               }
-       },
-
-       languages: {
-               extend: function (id, redef) {
-                       var lang = _.util.clone(_.languages[id]);
-
-                       for (var key in redef) {
-                               lang[key] = redef[key];
-                       }
-
-                       return lang;
-               },
-
-               /**
-                * Insert a token before another token in a language literal
-                * As this needs to recreate the object (we cannot actually insert before keys in object literals),
-                * we cannot just provide an object, we need anobject and a key.
-                * @param inside The key (or language id) of the parent
-                * @param before The key to insert before. If not provided, the function appends instead.
-                * @param insert Object with the key/value pairs to insert
-                * @param root The object that contains `inside`. If equal to Prism.languages, it can be omitted.
-                */
-               insertBefore: function (inside, before, insert, root) {
-                       root = root || _.languages;
-                       var grammar = root[inside];
-
-                       if (arguments.length == 2) {
-                               insert = arguments[1];
-
-                               for (var newToken in insert) {
-                                       if (insert.hasOwnProperty(newToken)) {
-                                               grammar[newToken] = insert[newToken];
-                                       }
-                               }
-
-                               return grammar;
-                       }
-
-                       var ret = {};
-
-                       for (var token in grammar) {
-
-                               if (grammar.hasOwnProperty(token)) {
-
-                                       if (token == before) {
-
-                                               for (var newToken in insert) {
-
-                                                       if (insert.hasOwnProperty(newToken)) {
-                                                               ret[newToken] = insert[newToken];
-                                                       }
-                                               }
-                                       }
-
-                                       ret[token] = grammar[token];
-                               }
-                       }
-
-                       var old = root[inside];
-                       root[inside] = ret;
-
-                       // Update references in other language definitions
-                       _.languages.DFS(_.languages, function(key, value) {
-                               if (value === old && key != inside) {
-                                       this[key] = ret;
-                               }
-                       });
-
-                       return ret;
-               },
-
-               // Traverse a language definition with Depth First Search
-               DFS: function(o, callback, type, visited) {
-                       visited = visited || {};
-                       for (var i in o) {
-                               if (o.hasOwnProperty(i)) {
-                                       callback.call(o, i, o[i], type || i);
-
-                                       if (_.util.type(o[i]) === 'Object' && !visited[_.util.objId(o[i])]) {
-                                               visited[_.util.objId(o[i])] = true;
-                                               _.languages.DFS(o[i], callback, null, visited);
-                                       }
-                                       else if (_.util.type(o[i]) === 'Array' && !visited[_.util.objId(o[i])]) {
-                                               visited[_.util.objId(o[i])] = true;
-                                               _.languages.DFS(o[i], callback, i, visited);
-                                       }
-                               }
-                       }
-               }
-       },
-       plugins: {},
-
-       highlightAll: function(async, callback) {
-               _.highlightAllUnder(document, async, callback);
-       },
-
-       highlightAllUnder: function(container, async, callback) {
-               var env = {
-                       callback: callback,
-                       selector: 'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'
-               };
-
-               _.hooks.run("before-highlightall", env);
-
-               var elements = env.elements || container.querySelectorAll(env.selector);
-
-               for (var i=0, element; element = elements[i++];) {
-                       _.highlightElement(element, async === true, env.callback);
-               }
-       },
-
-       highlightElement: function(element, async, callback) {
-               // Find language
-               var language, grammar, parent = element;
-
-               while (parent && !lang.test(parent.className)) {
-                       parent = parent.parentNode;
-               }
-
-               if (parent) {
-                       language = (parent.className.match(lang) || [,''])[1].toLowerCase();
-                       grammar = _.languages[language];
-               }
-
-               // Set language on the element, if not present
-               element.className = element.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;
-
-               if (element.parentNode) {
-                       // Set language on the parent, for styling
-                       parent = element.parentNode;
-
-                       if (/pre/i.test(parent.nodeName)) {
-                               parent.className = parent.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;
-                       }
-               }
-
-               var code = element.textContent;
-
-               var env = {
-                       element: element,
-                       language: language,
-                       grammar: grammar,
-                       code: code
-               };
-
-               _.hooks.run('before-sanity-check', env);
-
-               if (!env.code || !env.grammar) {
-                       if (env.code) {
-                               _.hooks.run('before-highlight', env);
-                               env.element.textContent = env.code;
-                               _.hooks.run('after-highlight', env);
-                       }
-                       _.hooks.run('complete', env);
-                       return;
-               }
-
-               _.hooks.run('before-highlight', env);
-
-               if (async && _self.Worker) {
-                       var worker = new Worker(_.filename);
-
-                       worker.onmessage = function(evt) {
-                               env.highlightedCode = evt.data;
-
-                               _.hooks.run('before-insert', env);
-
-                               env.element.innerHTML = env.highlightedCode;
-
-                               callback && callback.call(env.element);
-                               _.hooks.run('after-highlight', env);
-                               _.hooks.run('complete', env);
-                       };
-
-                       worker.postMessage(JSON.stringify({
-                               language: env.language,
-                               code: env.code,
-                               immediateClose: true
-                       }));
-               }
-               else {
-                       env.highlightedCode = _.highlight(env.code, env.grammar, env.language);
-
-                       _.hooks.run('before-insert', env);
-
-                       env.element.innerHTML = env.highlightedCode;
-
-                       callback && callback.call(element);
-
-                       _.hooks.run('after-highlight', env);
-                       _.hooks.run('complete', env);
-               }
-       },
-
-       highlight: function (text, grammar, language) {
-               var env = {
-                       code: text,
-                       grammar: grammar,
-                       language: language
-               };
-               _.hooks.run('before-tokenize', env);
-               env.tokens = _.tokenize(env.code, env.grammar);
-               _.hooks.run('after-tokenize', env);
-               return Token.stringify(_.util.encode(env.tokens), env.language);
-       },
-
-       matchGrammar: function (text, strarr, grammar, index, startPos, oneshot, target) {
-               var Token = _.Token;
-
-               for (var token in grammar) {
-                       if(!grammar.hasOwnProperty(token) || !grammar[token]) {
-                               continue;
-                       }
-
-                       if (token == target) {
-                               return;
-                       }
-
-                       var patterns = grammar[token];
-                       patterns = (_.util.type(patterns) === "Array") ? patterns : [patterns];
-
-                       for (var j = 0; j < patterns.length; ++j) {
-                               var pattern = patterns[j],
-                                       inside = pattern.inside,
-                                       lookbehind = !!pattern.lookbehind,
-                                       greedy = !!pattern.greedy,
-                                       lookbehindLength = 0,
-                                       alias = pattern.alias;
-
-                               if (greedy && !pattern.pattern.global) {
-                                       // Without the global flag, lastIndex won't work
-                                       var flags = pattern.pattern.toString().match(/[imuy]*$/)[0];
-                                       pattern.pattern = RegExp(pattern.pattern.source, flags + "g");
-                               }
-
-                               pattern = pattern.pattern || pattern;
-
-                               // Don’t cache length as it changes during the loop
-                               for (var i = index, pos = startPos; i < strarr.length; pos += strarr[i].length, ++i) {
-
-                                       var str = strarr[i];
-
-                                       if (strarr.length > text.length) {
-                                               // Something went terribly wrong, ABORT, ABORT!
-                                               return;
-                                       }
-
-                                       if (str instanceof Token) {
-                                               continue;
-                                       }
-
-                                       if (greedy && i != strarr.length - 1) {
-                                               pattern.lastIndex = pos;
-                                               var match = pattern.exec(text);
-                                               if (!match) {
-                                                       break;
-                                               }
-
-                                               var from = match.index + (lookbehind ? match[1].length : 0),
-                                                   to = match.index + match[0].length,
-                                                   k = i,
-                                                   p = pos;
-
-                                               for (var len = strarr.length; k < len && (p < to || (!strarr[k].type && !strarr[k - 1].greedy)); ++k) {
-                                                       p += strarr[k].length;
-                                                       // Move the index i to the element in strarr that is closest to from
-                                                       if (from >= p) {
-                                                               ++i;
-                                                               pos = p;
-                                                       }
-                                               }
-
-                                               // If strarr[i] is a Token, then the match starts inside another Token, which is invalid
-                                               if (strarr[i] instanceof Token) {
-                                                       continue;
-                                               }
-
-                                               // Number of tokens to delete and replace with the new match
-                                               delNum = k - i;
-                                               str = text.slice(pos, p);
-                                               match.index -= pos;
-                                       } else {
-                                               pattern.lastIndex = 0;
-
-                                               var match = pattern.exec(str),
-                                                       delNum = 1;
-                                       }
-
-                                       if (!match) {
-                                               if (oneshot) {
-                                                       break;
-                                               }
-
-                                               continue;
-                                       }
-
-                                       if(lookbehind) {
-                                               lookbehindLength = match[1] ? match[1].length : 0;
-                                       }
-
-                                       var from = match.index + lookbehindLength,
-                                           match = match[0].slice(lookbehindLength),
-                                           to = from + match.length,
-                                           before = str.slice(0, from),
-                                           after = str.slice(to);
-
-                                       var args = [i, delNum];
-
-                                       if (before) {
-                                               ++i;
-                                               pos += before.length;
-                                               args.push(before);
-                                       }
-
-                                       var wrapped = new Token(token, inside? _.tokenize(match, inside) : match, alias, match, greedy);
-
-                                       args.push(wrapped);
-
-                                       if (after) {
-                                               args.push(after);
-                                       }
-
-                                       Array.prototype.splice.apply(strarr, args);
-
-                                       if (delNum != 1)
-                                               _.matchGrammar(text, strarr, grammar, i, pos, true, token);
-
-                                       if (oneshot)
-                                               break;
-                               }
-                       }
-               }
-       },
-
-       tokenize: function(text, grammar, language) {
-               var strarr = [text];
-
-               var rest = grammar.rest;
-
-               if (rest) {
-                       for (var token in rest) {
-                               grammar[token] = rest[token];
-                       }
-
-                       delete grammar.rest;
-               }
-
-               _.matchGrammar(text, strarr, grammar, 0, 0, false);
-
-               return strarr;
-       },
-
-       hooks: {
-               all: {},
-
-               add: function (name, callback) {
-                       var hooks = _.hooks.all;
-
-                       hooks[name] = hooks[name] || [];
-
-                       hooks[name].push(callback);
-               },
-
-               run: function (name, env) {
-                       var callbacks = _.hooks.all[name];
-
-                       if (!callbacks || !callbacks.length) {
-                               return;
-                       }
-
-                       for (var i=0, callback; callback = callbacks[i++];) {
-                               callback(env);
-                       }
-               }
-       }
-};
-
-var Token = _.Token = function(type, content, alias, matchedStr, greedy) {
-       this.type = type;
-       this.content = content;
-       this.alias = alias;
-       // Copy of the full string this token was created from
-       this.length = (matchedStr || "").length|0;
-       this.greedy = !!greedy;
-};
-
-Token.stringify = function(o, language, parent) {
-       if (typeof o == 'string') {
-               return o;
-       }
-
-       if (_.util.type(o) === 'Array') {
-               return o.map(function(element) {
-                       return Token.stringify(element, language, o);
-               }).join('');
-       }
-
-       var env = {
-               type: o.type,
-               content: Token.stringify(o.content, language, parent),
-               tag: 'span',
-               classes: ['token', o.type],
-               attributes: {},
-               language: language,
-               parent: parent
-       };
-
-       if (o.alias) {
-               var aliases = _.util.type(o.alias) === 'Array' ? o.alias : [o.alias];
-               Array.prototype.push.apply(env.classes, aliases);
-       }
-
-       _.hooks.run('wrap', env);
-
-       var attributes = Object.keys(env.attributes).map(function(name) {
-               return name + '="' + (env.attributes[name] || '').replace(/"/g, '&quot;') + '"';
-       }).join(' ');
-
-       return '<' + env.tag + ' class="' + env.classes.join(' ') + '"' + (attributes ? ' ' + attributes : '') + '>' + env.content + '</' + env.tag + '>';
-
-};
-
-if (!_self.document) {
-       if (!_self.addEventListener) {
-               // in Node.js
-               return _self.Prism;
-       }
-
-       if (!_.disableWorkerMessageHandler) {
-               // In worker
-               _self.addEventListener('message', function (evt) {
-                       var message = JSON.parse(evt.data),
-                               lang = message.language,
-                               code = message.code,
-                               immediateClose = message.immediateClose;
-
-                       _self.postMessage(_.highlight(code, _.languages[lang], lang));
-                       if (immediateClose) {
-                               _self.close();
-                       }
-               }, false);
-       }
-
-       return _self.Prism;
-}
-
-//Get current script and highlight
-var script = document.currentScript || [].slice.call(document.getElementsByTagName("script")).pop();
-
-if (script) {
-       _.filename = script.src;
-
-       if (!_.manual && !script.hasAttribute('data-manual')) {
-               if(document.readyState !== "loading") {
-                       if (window.requestAnimationFrame) {
-                               window.requestAnimationFrame(_.highlightAll);
-                       } else {
-                               window.setTimeout(_.highlightAll, 16);
-                       }
-               }
-               else {
-                       document.addEventListener('DOMContentLoaded', _.highlightAll);
-               }
-       }
-}
-
-return _self.Prism;
-
-})();
-
-if (typeof module !== 'undefined' && module.exports) {
-       module.exports = Prism;
-}
-
-// hack for components to work correctly in node.js
-if (typeof global !== 'undefined') {
-       global.Prism = Prism;
-}
-;
-
-define("prism/prism", function(){});
-
-/**
- * Augments the Prism syntax highlighter with additional functions.
- * 
- * @author     Tim Duesterhus
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Prism
- */
-
-window.Prism = window.Prism || {}
-window.Prism.manual = true
-
-define('WoltLabSuite/Core/Prism',['prism/prism'], function () {
-       Prism.wscSplitIntoLines = function (container) {
-               var frag = document.createDocumentFragment();
-               var lineNo = 1;
-               var it, node, line;
-               
-               function newLine() {
-                       var line = elCreate('span');
-                       elData(line, 'number', lineNo++);
-                       frag.appendChild(line);
-                       
-                       return line;
-               }
-               
-               // IE11 expects a fourth, non-standard, parameter (entityReferenceExpansion) and a valid function as third
-               it = document.createNodeIterator(container, NodeFilter.SHOW_TEXT, function () {
-                       return NodeFilter.FILTER_ACCEPT;
-               }, false);
-               
-               line = newLine(lineNo);
-               while (node = it.nextNode()) {
-                       node.data.split(/\r?\n/).forEach(function (codeLine, index) {
-                               var current, parent;
-                               
-                               // We are behind a newline, insert \n and create new container.
-                               if (index >= 1) {
-                                       line.appendChild(document.createTextNode("\n"));
-                                       line = newLine(lineNo);
-                               }
-                               
-                               current = document.createTextNode(codeLine);
-                               
-                               // Copy hierarchy (to preserve CSS classes).
-                               parent = node.parentNode
-                               while (parent !== container) {
-                                       var clone = parent.cloneNode(false);
-                                       clone.appendChild(current);
-                                       current = clone;
-                                       parent = parent.parentNode;
-                               }
-                               
-                               line.appendChild(current);
-                       });
-               }
-               
-               return frag;
-       };
-
-       return Prism;
-});
-
-/**
- * Uploads file via AJAX.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Upload
- */
-define('WoltLabSuite/Core/Upload',['AjaxRequest', 'Core', 'Dom/ChangeListener', 'Language', 'Dom/Util', 'Dom/Traverse'], function(AjaxRequest, Core, DomChangeListener, Language, DomUtil, DomTraverse) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       _createButton: function() {},
-                       _createFileElement: function() {},
-                       _createFileElements: function() {},
-                       _failure: function() {},
-                       _getParameters: function() {},
-                       _insertButton: function() {},
-                       _progress: function() {},
-                       _removeButton: function() {},
-                       _success: function() {},
-                       _upload: function() {},
-                       _uploadFiles: function() {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * @constructor
-        */
-       function Upload(buttonContainerId, targetId, options) {
-               options = options || {};
-               
-               if (options.className === undefined) {
-                       throw new Error("Missing class name.");
-               }
-               
-               // set default options
-               this._options = Core.extend({
-                       // name of the PHP action
-                       action: 'upload',
-                       // is true if multiple files can be uploaded at once
-                       multiple: false,
-                       // name if the upload field
-                       name: '__files[]',
-                       // is true if every file from a multi-file selection is uploaded in its own request
-                       singleFileRequests: false,
-                       // url for uploading file
-                       url: 'index.php?ajax-upload/&t=' + SECURITY_TOKEN
-               }, options);
-               
-               this._options.url = Core.convertLegacyUrl(this._options.url);
-               if (this._options.url.indexOf('index.php') === 0) {
-                       this._options.url = WSC_API_URL + this._options.url;
-               }
-               
-               this._buttonContainer = elById(buttonContainerId);
-               if (this._buttonContainer === null) {
-                       throw new Error("Element id '" + buttonContainerId + "' is unknown.");
-               }
-               
-               this._target = elById(targetId);
-               if (targetId === null) {
-                       throw new Error("Element id '" + targetId + "' is unknown.");
-               }
-               if (options.multiple && this._target.nodeName !== 'UL' && this._target.nodeName !== 'OL' && this._target.nodeName !== 'TBODY') {
-                       throw new Error("Target element has to be list or table body if uploading multiple files is supported.");
-               }
-               
-               this._fileElements = [];
-               this._internalFileId = 0;
-               
-               // upload ids that belong to an upload of multiple files at once
-               this._multiFileUploadIds = [];
-               
-               this._createButton();
-       }
-       Upload.prototype = {
-               /**
-                * Creates the upload button.
-                */
-               _createButton: function() {
-                       this._fileUpload = elCreate('input');
-                       elAttr(this._fileUpload, 'type', 'file');
-                       elAttr(this._fileUpload, 'name', this._options.name);
-                       if (this._options.multiple) {
-                               elAttr(this._fileUpload, 'multiple', 'true');
-                       }
-                       this._fileUpload.addEventListener('change', this._upload.bind(this));
-                       
-                       this._button = elCreate('p');
-                       this._button.className = 'button uploadButton';
-                       elAttr(this._button, 'role', 'button');
-                       elAttr(this._button, 'tabindex', '0');
-                       
-                       var span = elCreate('span');
-                       span.textContent = Language.get('wcf.global.button.upload');
-                       this._button.appendChild(span);
-                       
-                       DomUtil.prepend(this._fileUpload, this._button);
-                       
-                       this._insertButton();
-                       
-                       DomChangeListener.trigger();
-               },
-               
-               /**
-                * Creates the document element for an uploaded file.
-                * 
-                * @param       {File}          file            uploaded file
-                * @return      {HTMLElement}
-                */
-               _createFileElement: function(file) {
-                       var progress = elCreate('progress');
-                       elAttr(progress, 'max', 100);
-                       
-                       if (this._target.nodeName === 'OL' || this._target.nodeName === 'UL') {
-                               var li = elCreate('li');
-                               li.innerText = file.name;
-                               li.appendChild(progress);
-                               
-                               this._target.appendChild(li);
-                               
-                               return li;
-                       }
-                       else if (this._target.nodeName === 'TBODY') {
-                               return this._createFileTableRow(file);
-                       }
-                       else {
-                               var p = elCreate('p');
-                               p.appendChild(progress);
-                               
-                               this._target.appendChild(p);
-                               
-                               return p;
-                       }
-               },
-               
-               /**
-                * Creates the document elements for uploaded files.
-                * 
-                * @param       {(FileList|Array.<File>)}       files           uploaded files
-                */
-               _createFileElements: function(files) {
-                       if (files.length) {
-                               var uploadId = this._fileElements.length;
-                               this._fileElements[uploadId] = [];
-                               
-                               for (var i = 0, length = files.length; i < length; i++) {
-                                       var file = files[i];
-                                       var fileElement = this._createFileElement(file);
-                                       
-                                       if (!fileElement.classList.contains('uploadFailed')) {
-                                               elData(fileElement, 'filename', file.name);
-                                               elData(fileElement, 'internal-file-id', this._internalFileId++);
-                                               this._fileElements[uploadId][i] = fileElement;
-                                       }
-                               }
-                               
-                               DomChangeListener.trigger();
-                               
-                               return uploadId;
-                       }
-                       
-                       return null;
-               },
-               
-               _createFileTableRow: function(file) {
-                       throw new Error("Has to be implemented in subclass.");
-               },
-               
-               /**
-                * Handles a failed file upload.
-                * 
-                * @param       {int}                   uploadId        identifier of a file upload
-                * @param       {object<string, *>}     data            response data
-                * @param       {string}                responseText    response
-                * @param       {XMLHttpRequest}        xhr             request object
-                * @param       {object<string, *>}     requestOptions  options used to send AJAX request
-                * @return      {boolean}       true if the error message should be shown
-                */
-               _failure: function(uploadId, data, responseText, xhr, requestOptions) {
-                       // does nothing
-                       return true;
-               },
-               
-               /**
-                * Return additional parameters for upload requests.
-                * 
-                * @return      {object<string, *>}     additional parameters
-                */
-               _getParameters: function() {
-                       return {};
-               },
-               
-               /**
-                * Return additional form data for upload requests.
-                * 
-                * @return      {object<string, *>}     additional form data
-                * @since       5.2
-                */
-               _getFormData: function() {
-                       return {};
-               },
-               
-               /**
-                * Inserts the created button to upload files into the button container.
-                */
-               _insertButton: function() {
-                       DomUtil.prepend(this._button, this._buttonContainer);
-               },
-               
-               /**
-                * Updates the progress of an upload.
-                * 
-                * @param       {int}                           uploadId        internal upload identifier
-                * @param       {XMLHttpRequestProgressEvent}   event           progress event object
-                */
-               _progress: function(uploadId, event) {
-                       var percentComplete = Math.round(event.loaded / event.total * 100);
-                       
-                       for (var i in this._fileElements[uploadId]) {
-                               var progress = elByTag('PROGRESS', this._fileElements[uploadId][i]);
-                               if (progress.length === 1) {
-                                       elAttr(progress[0], 'value', percentComplete);
-                               }
-                       }
-               },
-               
-               /**
-                * Removes the button to upload files.
-                */
-               _removeButton: function() {
-                       elRemove(this._button);
-                       
-                       DomChangeListener.trigger();
-               },
-               
-               /**
-                * Handles a successful file upload.
-                * 
-                * @param       {int}                   uploadId        identifier of a file upload
-                * @param       {object<string, *>}     data            response data
-                * @param       {string}                responseText    response
-                * @param       {XMLHttpRequest}        xhr             request object
-                * @param       {object<string, *>}     requestOptions  options used to send AJAX request
-                */
-               _success: function(uploadId, data, responseText, xhr, requestOptions) {
-                       // does nothing
-               },
-               
-               /**
-                * File input change callback to upload files.
-                * 
-                * @param       {Event}         event           input change event object
-                * @param       {File}          file            uploaded file
-                * @param       {Blob}          blob            file blob
-                * @return      {(int|Array.<int>|null)}        identifier(s) for the uploaded files
-                */
-               _upload: function(event, file, blob) {
-                       // remove failed upload elements first
-                       var failedUploads = DomTraverse.childrenByClass(this._target, 'uploadFailed');
-                       for (var i = 0, length = failedUploads.length; i < length; i++) {
-                               elRemove(failedUploads[i]);
-                       }
-                       
-                       var uploadId = null;
-                       
-                       var files = [];
-                       if (file) {
-                               files.push(file);
-                       }
-                       else if (blob) {
-                               var fileExtension = '';
-                               switch (blob.type) {
-                                       case 'image/jpeg':
-                                               fileExtension = '.jpg';
-                                       break;
-                                       
-                                       case 'image/gif':
-                                               fileExtension = '.gif';
-                                       break;
-                                       
-                                       case 'image/png':
-                                               fileExtension = '.png';
-                                       break;
-                               }
-                               
-                               files.push({
-                                       name: 'pasted-from-clipboard' + fileExtension
-                               });
-                       }
-                       else {
-                               files = this._fileUpload.files;
-                       }
-                       
-                       if (files.length && this.validateUpload(files)) {
-                               if (this._options.singleFileRequests) {
-                                       uploadId = [];
-                                       for (var i = 0, length = files.length; i < length; i++) {
-                                               var localUploadId = this._uploadFiles([ files[i] ], blob);
-                                               
-                                               if (files.length !== 1) {
-                                                       this._multiFileUploadIds.push(localUploadId)
-                                               }
-                                               uploadId.push(localUploadId);
-                                       }
-                               }
-                               else {
-                                       uploadId = this._uploadFiles(files, blob);
-                               }
-                       }
-                       
-                       // re-create upload button to effectively reset the 'files'
-                       // property of the input element
-                       this._removeButton();
-                       this._createButton();
-                       
-                       return uploadId;
-               },
-               
-               /**
-                * Validates the upload before uploading them.
-                * 
-                * @param       {(FileList|Array.<File>)}       files           uploaded files
-                * @return      {boolean}
-                * @since       5.2
-                */
-               validateUpload: function(files) {
-                       return true;
-               },
-               
-               /**
-                * Sends the request to upload files.
-                * 
-                * @param       {(FileList|Array.<File>)}       files           uploaded files
-                * @param       {Blob}                          blob            file blob
-                * @return      {(int|null)}    identifier for the uploaded files
-                */
-               _uploadFiles: function(files, blob) {
-                       var uploadId = this._createFileElements(files);
-                       
-                       // no more files left, abort
-                       if (!this._fileElements[uploadId].length) {
-                               return null;
-                       }
-                       
-                       var formData = new FormData();
-                       for (var i = 0, length = files.length; i < length; i++) {
-                               if (this._fileElements[uploadId][i]) {
-                                       var internalFileId = elData(this._fileElements[uploadId][i], 'internal-file-id');
-                                       
-                                       if (blob) {
-                                               formData.append('__files[' + internalFileId + ']', blob, files[i].name);
-                                       }
-                                       else {
-                                               formData.append('__files[' + internalFileId + ']', files[i]);
-                                       }
-                               }
-                       }
-                       
-                       formData.append('actionName', this._options.action);
-                       formData.append('className', this._options.className);
-                       if (this._options.action === 'upload') {
-                               formData.append('interfaceName', 'wcf\\data\\IUploadAction');
-                       }
-                       
-                       // recursively append additional parameters to form data
-                       var appendFormData = function(parameters, prefix) {
-                               prefix = prefix || '';
-                               
-                               for (var name in parameters) {
-                                       if (typeof parameters[name] === 'object') {
-                                               var newPrefix = prefix.length === 0 ? name : prefix + '[' + name + ']';
-                                               appendFormData(parameters[name], newPrefix);
-                                       }
-                                       else {
-                                               var dataName = prefix.length === 0 ? name : prefix + '[' + name + ']';
-                                               formData.append(dataName, parameters[name]);
-                                       }
-                               }
-                       };
-                       
-                       appendFormData(this._getParameters(), 'parameters');
-                       appendFormData(this._getFormData());
-                       
-                       var request = new AjaxRequest({
-                               data: formData,
-                               contentType: false,
-                               failure: this._failure.bind(this, uploadId),
-                               silent: true,
-                               success: this._success.bind(this, uploadId),
-                               uploadProgress: this._progress.bind(this, uploadId),
-                               url: this._options.url,
-                               withCredentials: true
-                       });
-                       request.sendRequest();
-                       
-                       return uploadId;
-               },
-               
-               /**
-                * Returns true if there are any pending uploads handled by this
-                * upload manager.
-                * 
-                * @return      {boolean}
-                * @since       5.2
-                */
-               hasPendingUploads: function() {
-                       for (var uploadId in this._fileElements) {
-                               for (var i in this._fileElements[uploadId]) {
-                                       var progress = elByTag('PROGRESS', this._fileElements[uploadId][i]);
-                                       if (progress.length === 1) {
-                                               return true;
-                                       }
-                               }
-                       }
-                       
-                       return false;
-               },
-               
-               /**
-                * Uploads the given file blob.
-                * 
-                * @param       {Blob}          blob            file blob
-                * @return      {int}           identifier for the uploaded file
-                */
-               uploadBlob: function(blob) {
-                       return this._upload(null, null, blob);
-               },
-               
-               /**
-                * Uploads the given file.
-                *
-                * @param       {File}          file            uploaded file
-                * @return      {int}           identifier(s) for the uploaded file
-                */
-               uploadFile: function(file) {
-                       return this._upload(null, file);
-               }
-       };
-       
-       return Upload;
-});
-
-/**
- * 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     WoltLabSuite/Core/User
- */
-define('WoltLabSuite/Core/User',[], 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;
-               }
-       };
-});
-
-/**
- * Provides a utility class to issue JSONP requests.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ajax/Jsonp
- */
-define('WoltLabSuite/Core/Ajax/Jsonp',['Core'], function(Core) {
-       "use strict";
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ajax/Jsonp
-        */
-       return {
-               /**
-                * Issues a JSONP request.
-                * 
-                * @param       {string}                url             source URL, must not contain callback parameter
-                * @param       {function}              success         success callback
-                * @param       {function=}             failure         timeout callback
-                * @param       {object<string, *>=}    options         request options
-                */
-               send: function(url, success, failure, options) {
-                       url = (typeof url === 'string') ? url.trim() : '';
-                       if (url.length === 0) {
-                               throw new Error("Expected a non-empty string for parameter 'url'.");
-                       }
-                       
-                       if (typeof success !== 'function') {
-                               throw new TypeError("Expected a valid callback function for parameter 'success'.");
-                       }
-                       
-                       options = Core.extend({
-                               parameterName: 'callback',
-                               timeout: 10
-                       }, options || {});
-                       
-                       var callbackName = 'wcf_jsonp_' + Core.getUuid().replace(/-/g, '').substr(0, 8);
-                       var script;
-                       
-                       var timeout = window.setTimeout(function() {
-                               if (typeof failure === 'function') {
-                                       failure();
-                               }
-                               
-                               window[callbackName] = undefined;
-                               elRemove(script);
-                       }, (~~options.timeout || 10) * 1000);
-                       
-                       window[callbackName] = function() {
-                               window.clearTimeout(timeout);
-                               
-                               success.apply(null, arguments);
-                               
-                               window[callbackName] = undefined;
-                               elRemove(script);
-                       };
-                       
-                       url += (url.indexOf('?') === -1) ? '?' : '&';
-                       url += options.parameterName + '=' + callbackName;
-                       
-                       script = elCreate('script');
-                       script.async = true;
-                       elAttr(script, 'src', url);
-                       
-                       document.head.appendChild(script);
-               }
-       };
-});
-
-/**
- * Simple notification overlay.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Notification
- */
-define('WoltLabSuite/Core/Ui/Notification',['Language'], function(Language) {
-       "use strict";
-       
-       var _busy = false;
-       var _callback = null;
-       var _message = null;
-       var _notificationElement = null;
-       var _timeout = null;
-       
-       var _callbackHide = null;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Notification
-        */
-       var UiNotification = {
-               /**
-                * Shows a notification.
-                * 
-                * @param       {string}        message         message
-                * @param       {function=}     callback        callback function to be executed once notification is being hidden
-                * @param       {string=}       cssClassName    alternate CSS class name, defaults to 'success'
-                */
-               show: function(message, callback, cssClassName) {
-                       if (_busy) {
-                               return;
-                       }
-                       
-                       this._init();
-                       
-                       _callback = (typeof callback === 'function') ? callback : null;
-                       _message.className = cssClassName || 'success';
-                       _message.textContent = Language.get(message || 'wcf.global.success');
-                       
-                       _busy = true;
-                       
-                       _notificationElement.classList.add('active');
-                       
-                       _timeout = setTimeout(_callbackHide, 2000);
-               },
-               
-               /**
-                * Initializes the UI elements.
-                */
-               _init: function() {
-                       if (_notificationElement === null) {
-                               _callbackHide = this._hide.bind(this);
-                               
-                               _notificationElement = elCreate('div');
-                               _notificationElement.id = 'systemNotification';
-                               
-                               _message = elCreate('p');
-                               _message.addEventListener(WCF_CLICK_EVENT, _callbackHide);
-                               _notificationElement.appendChild(_message);
-                               
-                               document.body.appendChild(_notificationElement);
-                       }
-               },
-               
-               /**
-                * Hides the notification and invokes the callback if provided.
-                */
-               _hide: function() {
-                       clearTimeout(_timeout);
-                       
-                       _notificationElement.classList.remove('active');
-                       
-                       if (_callback !== null) {
-                               _callback();
-                       }
-                       
-                       _busy = false;
-               }
-       };
-       
-       return UiNotification;
-});
-
-define('prism/prism-meta',[],function(){return /*START*/{"markup":{"title":"Markup","file":"markup"},"html":{"title":"HTML","file":"markup"},"xml":{"title":"XML","file":"markup"},"svg":{"title":"SVG","file":"markup"},"mathml":{"title":"MathML","file":"markup"},"css":{"title":"CSS","file":"css"},"clike":{"title":"C-like","file":"clike"},"javascript":{"title":"JavaScript","file":"javascript"},"abap":{"title":"ABAP","file":"abap"},"actionscript":{"title":"ActionScript","file":"actionscript"},"ada":{"title":"Ada","file":"ada"},"apacheconf":{"title":"Apache Configuration","file":"apacheconf"},"apl":{"title":"APL","file":"apl"},"applescript":{"title":"AppleScript","file":"applescript"},"arduino":{"title":"Arduino","file":"arduino"},"arff":{"title":"ARFF","file":"arff"},"asciidoc":{"title":"AsciiDoc","file":"asciidoc"},"asm6502":{"title":"6502 Assembly","file":"asm6502"},"aspnet":{"title":"ASP.NET (C#)","file":"aspnet"},"autohotkey":{"title":"AutoHotkey","file":"autohotkey"},"autoit":{"title":"AutoIt","file":"autoit"},"bash":{"title":"Bash","file":"bash"},"basic":{"title":"BASIC","file":"basic"},"batch":{"title":"Batch","file":"batch"},"bison":{"title":"Bison","file":"bison"},"brainfuck":{"title":"Brainfuck","file":"brainfuck"},"bro":{"title":"Bro","file":"bro"},"c":{"title":"C","file":"c"},"csharp":{"title":"C#","file":"csharp"},"cpp":{"title":"C++","file":"cpp"},"coffeescript":{"title":"CoffeeScript","file":"coffeescript"},"clojure":{"title":"Clojure","file":"clojure"},"crystal":{"title":"Crystal","file":"crystal"},"csp":{"title":"Content-Security-Policy","file":"csp"},"css-extras":{"title":"CSS Extras","file":"css-extras"},"d":{"title":"D","file":"d"},"dart":{"title":"Dart","file":"dart"},"diff":{"title":"Diff","file":"diff"},"django":{"title":"Django/Jinja2","file":"django"},"docker":{"title":"Docker","file":"docker"},"eiffel":{"title":"Eiffel","file":"eiffel"},"elixir":{"title":"Elixir","file":"elixir"},"elm":{"title":"Elm","file":"elm"},"erb":{"title":"ERB","file":"erb"},"erlang":{"title":"Erlang","file":"erlang"},"fsharp":{"title":"F#","file":"fsharp"},"flow":{"title":"Flow","file":"flow"},"fortran":{"title":"Fortran","file":"fortran"},"gedcom":{"title":"GEDCOM","file":"gedcom"},"gherkin":{"title":"Gherkin","file":"gherkin"},"git":{"title":"Git","file":"git"},"glsl":{"title":"GLSL","file":"glsl"},"gml":{"title":"GameMaker Language","file":"gml"},"go":{"title":"Go","file":"go"},"graphql":{"title":"GraphQL","file":"graphql"},"groovy":{"title":"Groovy","file":"groovy"},"haml":{"title":"Haml","file":"haml"},"handlebars":{"title":"Handlebars","file":"handlebars"},"haskell":{"title":"Haskell","file":"haskell"},"haxe":{"title":"Haxe","file":"haxe"},"http":{"title":"HTTP","file":"http"},"hpkp":{"title":"HTTP Public-Key-Pins","file":"hpkp"},"hsts":{"title":"HTTP Strict-Transport-Security","file":"hsts"},"ichigojam":{"title":"IchigoJam","file":"ichigojam"},"icon":{"title":"Icon","file":"icon"},"inform7":{"title":"Inform 7","file":"inform7"},"ini":{"title":"Ini","file":"ini"},"io":{"title":"Io","file":"io"},"j":{"title":"J","file":"j"},"java":{"title":"Java","file":"java"},"jolie":{"title":"Jolie","file":"jolie"},"json":{"title":"JSON","file":"json"},"julia":{"title":"Julia","file":"julia"},"keyman":{"title":"Keyman","file":"keyman"},"kotlin":{"title":"Kotlin","file":"kotlin"},"latex":{"title":"LaTeX","file":"latex"},"less":{"title":"Less","file":"less"},"liquid":{"title":"Liquid","file":"liquid"},"lisp":{"title":"Lisp","file":"lisp"},"livescript":{"title":"LiveScript","file":"livescript"},"lolcode":{"title":"LOLCODE","file":"lolcode"},"lua":{"title":"Lua","file":"lua"},"makefile":{"title":"Makefile","file":"makefile"},"markdown":{"title":"Markdown","file":"markdown"},"markup-templating":{"title":"Markup templating","file":"markup-templating"},"matlab":{"title":"MATLAB","file":"matlab"},"mel":{"title":"MEL","file":"mel"},"mizar":{"title":"Mizar","file":"mizar"},"monkey":{"title":"Monkey","file":"monkey"},"n4js":{"title":"N4JS","file":"n4js"},"nasm":{"title":"NASM","file":"nasm"},"nginx":{"title":"nginx","file":"nginx"},"nim":{"title":"Nim","file":"nim"},"nix":{"title":"Nix","file":"nix"},"nsis":{"title":"NSIS","file":"nsis"},"objectivec":{"title":"Objective-C","file":"objectivec"},"ocaml":{"title":"OCaml","file":"ocaml"},"opencl":{"title":"OpenCL","file":"opencl"},"oz":{"title":"Oz","file":"oz"},"parigp":{"title":"PARI/GP","file":"parigp"},"parser":{"title":"Parser","file":"parser"},"pascal":{"title":"Pascal","file":"pascal"},"perl":{"title":"Perl","file":"perl"},"php":{"title":"PHP","file":"php"},"php-extras":{"title":"PHP Extras","file":"php-extras"},"plsql":{"title":"PL/SQL","file":"plsql"},"powershell":{"title":"PowerShell","file":"powershell"},"processing":{"title":"Processing","file":"processing"},"prolog":{"title":"Prolog","file":"prolog"},"properties":{"title":".properties","file":"properties"},"protobuf":{"title":"Protocol Buffers","file":"protobuf"},"pug":{"title":"Pug","file":"pug"},"puppet":{"title":"Puppet","file":"puppet"},"pure":{"title":"Pure","file":"pure"},"python":{"title":"Python","file":"python"},"q":{"title":"Q (kdb+ database)","file":"q"},"qore":{"title":"Qore","file":"qore"},"r":{"title":"R","file":"r"},"jsx":{"title":"React JSX","file":"jsx"},"tsx":{"title":"React TSX","file":"tsx"},"renpy":{"title":"Ren'py","file":"renpy"},"reason":{"title":"Reason","file":"reason"},"rest":{"title":"reST (reStructuredText)","file":"rest"},"rip":{"title":"Rip","file":"rip"},"roboconf":{"title":"Roboconf","file":"roboconf"},"ruby":{"title":"Ruby","file":"ruby"},"rust":{"title":"Rust","file":"rust"},"sas":{"title":"SAS","file":"sas"},"sass":{"title":"Sass (Sass)","file":"sass"},"scss":{"title":"Sass (Scss)","file":"scss"},"scala":{"title":"Scala","file":"scala"},"scheme":{"title":"Scheme","file":"scheme"},"smalltalk":{"title":"Smalltalk","file":"smalltalk"},"smarty":{"title":"Smarty","file":"smarty"},"sql":{"title":"SQL","file":"sql"},"soy":{"title":"Soy (Closure Template)","file":"soy"},"stylus":{"title":"Stylus","file":"stylus"},"swift":{"title":"Swift","file":"swift"},"tap":{"title":"TAP","file":"tap"},"tcl":{"title":"Tcl","file":"tcl"},"textile":{"title":"Textile","file":"textile"},"tt2":{"title":"Template Toolkit 2","file":"tt2"},"twig":{"title":"Twig","file":"twig"},"typescript":{"title":"TypeScript","file":"typescript"},"vbnet":{"title":"VB.Net","file":"vbnet"},"velocity":{"title":"Velocity","file":"velocity"},"verilog":{"title":"Verilog","file":"verilog"},"vhdl":{"title":"VHDL","file":"vhdl"},"vim":{"title":"vim","file":"vim"},"visual-basic":{"title":"Visual Basic","file":"visual-basic"},"wasm":{"title":"WebAssembly","file":"wasm"},"wiki":{"title":"Wiki markup","file":"wiki"},"xeora":{"title":"Xeora","file":"xeora"},"xojo":{"title":"Xojo (REALbasic)","file":"xojo"},"xquery":{"title":"XQuery","file":"xquery"},"yaml":{"title":"YAML","file":"yaml"}}/*END*/;});
-/**
- * Highlights code in the Code bbcode.
- * 
- * @author     Tim Duesterhus
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Bbcode/Code
- */
-define('WoltLabSuite/Core/Bbcode/Code',[
-               'Language', 'WoltLabSuite/Core/Ui/Notification', 'WoltLabSuite/Core/Clipboard', 'WoltLabSuite/Core/Prism', 'prism/prism-meta'
-       ],
-       function(
-               Language, UiNotification, Clipboard, Prism, PrismMeta
-       )
-{
-       "use strict";
-       
-       /** @const */ var CHUNK_SIZE = 50;
-       
-       // Define idleify() for piecewiese highlighting to not block the UI thread.
-       var idleify = function (callback) {
-               return function () {
-                       var args = arguments;
-                       return new Promise(function (resolve, reject) {
-                               var body = function () {
-                                       try {
-                                               resolve(callback.apply(null, args));
-                                       }
-                                       catch (e) {
-                                               reject(e);
-                                       }
-                               };
-                               
-                               if (window.requestIdleCallback) {
-                                       window.requestIdleCallback(body, { timeout: 5000 });
-                               }
-                               else {
-                                       setTimeout(body, 0);
-                               }
-                       });
-               };
-       };
-       
-       /**
-        * @constructor
-        */
-       function Code(container) {
-               var matches;
-               
-               this.container = container;
-               this.codeContainer = elBySel('.codeBoxCode > code', this.container);
-               this.language = null;
-               for (var i = 0; i < this.codeContainer.classList.length; i++) {
-                       if ((matches = this.codeContainer.classList[i].match(/language-(.*)/))) {
-                               this.language = matches[1];
-                       }
-               }
-       }
-       Code.processAll = function () {
-               elBySelAll('.codeBox:not([data-processed])', document, function (codeBox) {
-                       elData(codeBox, 'processed', '1');
-
-                       var handle = new Code(codeBox);
-                       if (handle.language) handle.highlight();
-                       handle.createCopyButton();
-               })
-       };
-       Code.prototype = {
-               createCopyButton: function () {
-                       var header = elBySel('.codeBoxHeader', this.container);
-                       var button = elCreate('span');
-                       button.className = 'icon icon24 fa-files-o pointer jsTooltip';
-                       button.setAttribute('title', Language.get('wcf.message.bbcode.code.copy'));
-                       button.addEventListener('click', function () {
-                               Clipboard.copyElementTextToClipboard(this.codeContainer).then(function () {
-                                       UiNotification.show(Language.get('wcf.message.bbcode.code.copy.success'));
-                               });
-                       }.bind(this));
-                       
-                       header.appendChild(button);
-               },
-               highlight: function () {
-                       if (!this.language) {
-                               return Promise.reject(new Error('No language detected'));
-                       }
-                       if (!PrismMeta[this.language]) {
-                               return Promise.reject(new Error('Unknown language ' + this.language));
-                       }
-                       
-                       this.container.classList.add('highlighting');
-                       
-                       return require(['prism/components/prism-' + PrismMeta[this.language].file])
-                       .then(idleify(function () {
-                               var grammar = Prism.languages[this.language];
-                               if (!grammar) {
-                                       throw new Error('Invalid language ' + language + ' given.');
-                               }
-                               
-                               var container = elCreate('div');
-                               container.innerHTML = Prism.highlight(this.codeContainer.textContent, grammar, this.language);
-                               return container;
-                       }.bind(this)))
-                       .then(idleify(function (container) {
-                               var highlighted = Prism.wscSplitIntoLines(container);
-                               var highlightedLines = elBySelAll('[data-number]', highlighted);
-                               var originalLines = elBySelAll('.codeBoxLine > span', this.codeContainer);
-                               
-                               if (highlightedLines.length !== originalLines.length) {
-                                       throw new Error('Unreachable');
-                               }
-                               
-                               var promises = [];
-                               for (var chunkStart = 0, max = highlightedLines.length; chunkStart < max; chunkStart += CHUNK_SIZE) {
-                                       promises.push(idleify(function (chunkStart) {
-                                               var chunkEnd = Math.min(chunkStart + CHUNK_SIZE, max);
-                                               
-                                               for (var offset = chunkStart; offset < chunkEnd; offset++) {
-                                                       originalLines[offset].parentNode.replaceChild(highlightedLines[offset], originalLines[offset]);
-                                               }
-                                       })(chunkStart));
-                               }
-                               return Promise.all(promises);
-                       }.bind(this)))
-                       .then(function () {
-                               this.container.classList.remove('highlighting');
-                               this.container.classList.add('highlighted');
-                       }.bind(this))
-               }
-       }
-       
-       return Code;
-});
-
-/**
- * Generic handler for collapsible bbcode boxes.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Bbcode/Collapsible
- */
-define('WoltLabSuite/Core/Bbcode/Collapsible',[], function() {
-       "use strict";
-       
-       var _containers = elByClass('jsCollapsibleBbcode');
-       
-       /**
-        * @exports     WoltLabSuite/Core/Bbcode/Collapsible
-        */
-       return {
-               observe: function() {
-                       var container, toggleButton;
-                       while (_containers.length) {
-                               container = _containers[0];
-                               
-                               // find the matching toggle button
-                               toggleButton = null;
-                               elBySelAll('.toggleButton:not(.jsToggleButtonEnabled)', container, function (button) {
-                                       //noinspection JSReferencingMutableVariableFromClosure
-                                       if (button.closest('.jsCollapsibleBbcode') === container) {
-                                               toggleButton = button;
-                                       }
-                               });
-                               
-                               if (toggleButton) {
-                                       (function (container, toggleButton) {
-                                               var toggle = function (event) {
-                                                       if (container.classList.toggle('collapsed')) {
-                                                               toggleButton.textContent = elData(toggleButton, 'title-expand');
-                                                               
-                                                               if (event instanceof Event) {
-                                                                       // negative top value means the upper boundary is not within the viewport
-                                                                       var top = container.getBoundingClientRect().top;
-                                                                       if (top < 0) {
-                                                                               var y = window.pageYOffset + (top - 100);
-                                                                               if (y < 0) y = 0;
-                                                                               window.scrollTo(window.pageXOffset, y);
-                                                                       }
-                                                               }
-                                                       }
-                                                       else {
-                                                               toggleButton.textContent = elData(toggleButton, 'title-collapse');
-                                                       }
-                                               };
-                                               
-                                               toggleButton.classList.add('jsToggleButtonEnabled');
-                                               toggleButton.addEventListener(WCF_CLICK_EVENT, toggle);
-                                               
-                                               // expand boxes that are initially scrolled
-                                               if (container.scrollTop !== 0) {
-                                                       toggle();
-                                               }
-                                               container.addEventListener('scroll', function () {
-                                                       if (container.classList.contains('collapsed')) {
-                                                               toggle();
-                                                       }
-                                               });
-                                       })(container, toggleButton);
-                               }
-                               
-                               container.classList.remove('jsCollapsibleBbcode');
-                       }
-               }
-       };
-});
-
-/**
- * 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     WoltLabSuite/Core/Controller/Captcha
- */
-define('WoltLabSuite/Core/Controller/Captcha',['Dictionary'], function(Dictionary) {
-       "use strict";
-       
-       var _captchas = new Dictionary();
-       
-       /**
-        * @exports     WoltLabSuite/Core/Controller/Captcha
-        */
-       return {
-               /**
-                * Registers a captcha with the given identifier and callback used to get captcha data.
-                * 
-                * @param       {string}        captchaId       captcha identifier
-                * @param       {function}      callback        callback to get captcha data
-                */
-               add: function(captchaId, callback) {
-                       if (_captchas.has(captchaId)) {
-                               throw new Error("Captcha with id '" + captchaId + "' is already registered.");
-                       }
-                       
-                       if (typeof callback !== 'function') {
-                               throw new TypeError("Expected a valid callback for parameter 'callback'.");
-                       }
-                       
-                       _captchas.set(captchaId, callback);
-               },
-               
-               /**
-                * Deletes the captcha with the given identifier.
-                * 
-                * @param       {string}        captchaId       identifier of the captcha to be deleted
-                */
-               'delete': function(captchaId) {
-                       if (!_captchas.has(captchaId)) {
-                               throw new Error("Unknown captcha with id '" + captchaId + "'.");
-                       }
-                       
-                       _captchas.delete(captchaId);
-               },
-               
-               /**
-                * Returns true if a captcha with the given identifier exists.
-                * 
-                * @param       {string}        captchaId       captcha identifier
-                * @return      {boolean}
-                */
-               has: function(captchaId) {
-                       return _captchas.has(captchaId);
-               },
-               
-               /**
-                * Returns the data of the captcha with the given identifier.
-                * 
-                * @param       {string}        captchaId       captcha identifier
-                * @return      {Object}        captcha data
-                */
-               getData: function(captchaId) {
-                       if (!_captchas.has(captchaId)) {
-                               throw new Error("Unknown captcha with id '" + captchaId + "'.");
-                       }
-                       
-                       return _captchas.get(captchaId)();
-               }
-       };
-});
-
-/**
- * Clipboard API Handler.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Controller/Clipboard
- */
-define(
-       'WoltLabSuite/Core/Controller/Clipboard',[
-               'Ajax',         'Core',     'Dictionary',      'EventHandler',
-               'Language',     'List',     'ObjectMap',       'Dom/ChangeListener',
-               'Dom/Traverse', 'Dom/Util', 'Ui/Confirmation', 'Ui/SimpleDropdown',
-               'WoltLabSuite/Core/Ui/Page/Action', 'Ui/Screen'
-       ],
-       function(
-               Ajax,            Core,       Dictionary,        EventHandler,
-               Language,        List,       ObjectMap,         DomChangeListener,
-               DomTraverse,     DomUtil,    UiConfirmation,    UiSimpleDropdown,
-               UiPageAction,    UiScreen
-       )
-{
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               return {
-                       setup: function() {},
-                       reload: function() {},
-                       _initContainers: function() {},
-                       _loadMarkedItems: function() {},
-                       _markAll: function() {},
-                       _mark: function() {},
-                       _saveState: function() {},
-                       _executeAction: function() {},
-                       _executeProxyAction: function() {},
-                       _unmarkAll: function() {},
-                       _ajaxSetup: function() {},
-                       _ajaxSuccess: function() {},
-                       _rebuildMarkings: function() {},
-                       hideEditor: function() {},
-                       showEditor: function() {},
-                       unmark: function() {}
-               };
-       }
-       
-       var _containers = new Dictionary();
-       var _editors = new Dictionary();
-       var _editorDropdowns = new Dictionary();
-       var _elements = elByClass('jsClipboardContainer');
-       var _itemData = new ObjectMap();
-       var _knownCheckboxes = new List();
-       var _options = {};
-       var _reloadPageOnSuccess = new Dictionary();
-       
-       var _callbackCheckbox = null;
-       var _callbackItem = null;
-       var _callbackUnmarkAll = null;
-       
-       var _specialCheckboxSelector = '.messageCheckboxLabel > input[type="checkbox"], .message .messageClipboardCheckbox > input[type="checkbox"], .messageGroupList .columnMark > label > input[type="checkbox"]';
-       
-       /**
-        * Clipboard API
-        * 
-        * @exports     WoltLabSuite/Core/Controller/Clipboard
-        */
-       return {
-               /**
-                * Initializes the clipboard API handler.
-                * 
-                * @param       {Object}        options         initialization options
-                */
-               setup: function(options) {
-                       if (!options.pageClassName) {
-                               throw new Error("Expected a non-empty string for parameter 'pageClassName'.");
-                       }
-                       
-                       if (_callbackCheckbox === null) {
-                               _callbackCheckbox = this._mark.bind(this);
-                               _callbackItem = this._executeAction.bind(this);
-                               _callbackUnmarkAll = this._unmarkAll.bind(this);
-                               
-                               _options = Core.extend({
-                                       hasMarkedItems: false,
-                                       pageClassNames: [options.pageClassName],
-                                       pageObjectId: 0
-                               }, options);
-                               
-                               delete _options.pageClassName;
-                       }
-                       else {
-                               if (options.pageObjectId) {
-                                       throw new Error("Cannot load secondary clipboard with page object id set.");
-                               }
-                               
-                               _options.pageClassNames.push(options.pageClassName);
-                       }
-                       
-                       if (!Element.prototype.matches) {
-                               Element.prototype.matches = Element.prototype.msMatchesSelector;
-                       }
-                       
-                       this._initContainers();
-                       
-                       if (_options.hasMarkedItems && _elements.length) {
-                               this._loadMarkedItems();
-                       }
-                       
-                       DomChangeListener.add('WoltLabSuite/Core/Controller/Clipboard', this._initContainers.bind(this));
-               },
-               
-               /**
-                * Reloads the clipboard data.
-                */
-               reload: function() {
-                       if (_containers.size) {
-                               this._loadMarkedItems();
-                       }
-               },
-               
-               /**
-                * Initializes clipboard containers.
-                */
-               _initContainers: function() {
-                       for (var i = 0, length = _elements.length; i < length; i++) {
-                               var container = _elements[i];
-                               var containerId = DomUtil.identify(container);
-                               var containerData = _containers.get(containerId);
-                               
-                               if (containerData === undefined) {
-                                       var markAll = elBySel('.jsClipboardMarkAll', container);
-                                       
-                                       if (markAll !== null) {
-                                               if (markAll.matches(_specialCheckboxSelector)) {
-                                                       var label = markAll.closest('label');
-                                                       elAttr(label, 'role', 'checkbox');
-                                                       elAttr(label, 'tabindex', '0');
-                                                       elAttr(label, 'aria-checked', false);
-                                                       elAttr(label, 'aria-label', Language.get('wcf.clipboard.item.markAll'));
-                                                       
-                                                       label.addEventListener('keyup', function (event) {
-                                                               if (event.keyCode === 13 || event.keyCode === 32) {
-                                                                       checkbox.click();
-                                                               }
-                                                       });
-                                               }
-                                               
-                                               elData(markAll, 'container-id', containerId);
-                                               markAll.addEventListener(WCF_CLICK_EVENT, this._markAll.bind(this));
-                                       }
-                                       
-                                       containerData = {
-                                               checkboxes: elByClass('jsClipboardItem', container),
-                                               element: container,
-                                               markAll: markAll,
-                                               markedObjectIds: new List()
-                                       };
-                                       _containers.set(containerId, containerData);
-                               }
-                               
-                               for (var j = 0, innerLength = containerData.checkboxes.length; j < innerLength; j++) {
-                                       var checkbox = containerData.checkboxes[j];
-                                       
-                                       if (!_knownCheckboxes.has(checkbox)) {
-                                               elData(checkbox, 'container-id', containerId);
-                                               
-                                               (function(checkbox) {
-                                                       if (checkbox.matches(_specialCheckboxSelector)) {
-                                                               var label = checkbox.closest('label');
-                                                               elAttr(label, 'role', 'checkbox');
-                                                               elAttr(label, 'tabindex', '0');
-                                                               elAttr(label, 'aria-checked', false);
-                                                               elAttr(label, 'aria-label', Language.get('wcf.clipboard.item.mark'));
-                                                               
-                                                               label.addEventListener('keyup', function (event) {
-                                                                       if (event.keyCode === 13 || event.keyCode === 32) {
-                                                                               checkbox.click();
-                                                                       }
-                                                               });
-                                                       }
-                                                       
-                                                       var link = checkbox.closest('a');
-                                                       if (link === null) {
-                                                               checkbox.addEventListener(WCF_CLICK_EVENT, _callbackCheckbox);
-                                                       }
-                                                       else {
-                                                               // Firefox will always trigger the link if the checkbox is
-                                                               // inside of one. Since 2000. Thanks Firefox. 
-                                                               checkbox.addEventListener(WCF_CLICK_EVENT, function (event) {
-                                                                       event.preventDefault();
-                                                                       
-                                                                       window.setTimeout(function () {
-                                                                               checkbox.checked = !checkbox.checked;
-                                                                               
-                                                                               _callbackCheckbox(null, checkbox);
-                                                                       }, 10);
-                                                               });
-                                                       }
-                                               })(checkbox);
-                                               
-                                               _knownCheckboxes.add(checkbox);
-                                       }
-                               }
-                       }
-               },
-               
-               /**
-                * Loads marked items from clipboard.
-                */
-               _loadMarkedItems: function() {
-                       Ajax.api(this, {
-                               actionName: 'getMarkedItems',
-                               parameters: {
-                                       pageClassNames: _options.pageClassNames,
-                                       pageObjectID: _options.pageObjectId
-                               }
-                       });
-               },
-               
-               /**
-                * Marks or unmarks all visible items at once.
-                * 
-                * @param       {object}        event   event object
-                */
-               _markAll: function(event) {
-                       var checkbox = event.currentTarget;
-                       var isMarked = (checkbox.nodeName !== 'INPUT' || checkbox.checked);
-                       
-                       if (elAttr(checkbox.parentNode, 'role') === 'checkbox') {
-                               elAttr(checkbox.parentNode, 'aria-checked', isMarked);
-                       }
-                       
-                       var objectIds = [];
-                       
-                       var containerId = elData(checkbox, 'container-id');
-                       var data = _containers.get(containerId);
-                       var type = elData(data.element, 'type');
-                       
-                       for (var i = 0, length = data.checkboxes.length; i < length; i++) {
-                               var item = data.checkboxes[i];
-                               var objectId = ~~elData(item, 'object-id');
-                               
-                               if (isMarked) {
-                                       if (!item.checked) {
-                                               item.checked = true;
-                                               
-                                               data.markedObjectIds.add(objectId);
-                                               objectIds.push(objectId);
-                                       }
-                               }
-                               else {
-                                       if (item.checked) {
-                                               item.checked = false;
-                                               
-                                               data.markedObjectIds['delete'](objectId);
-                                               objectIds.push(objectId);
-                                       }
-                               }
-                               
-                               if (elAttr(item.parentNode, 'role') === 'checkbox') {
-                                       elAttr(item.parentNode, 'aria-checked', isMarked);
-                               }
-                               
-                               var clipboardObject = DomTraverse.parentByClass(checkbox, 'jsClipboardObject');
-                               if (clipboardObject !== null) {
-                                       clipboardObject.classList[(isMarked ? 'addClass' : 'removeClass')]('jsMarked');
-                               }
-                       }
-                       
-                       this._saveState(type, objectIds, isMarked);
-               },
-               
-               /**
-                * Marks or unmarks an individual item.
-                * 
-                * @param       {object}        event           event object
-                * @param       {Element=}      checkbox        checkbox element
-                */
-               _mark: function(event, checkbox) {
-                       checkbox = (event instanceof Event) ? event.currentTarget : checkbox;
-                       var objectId = ~~elData(checkbox, 'object-id');
-                       var isMarked = checkbox.checked;
-                       var containerId = elData(checkbox, 'container-id');
-                       var data = _containers.get(containerId);
-                       var type = elData(data.element, 'type');
-                       
-                       var clipboardObject = DomTraverse.parentByClass(checkbox, 'jsClipboardObject');
-                       data.markedObjectIds[(isMarked ? 'add' : 'delete')](objectId);
-                       clipboardObject.classList[(isMarked) ? 'add' : 'remove']('jsMarked');
-                       
-                       if (data.markAll !== null) {
-                               var markedAll = true;
-                               for (var i = 0, length = data.checkboxes.length; i < length; i++) {
-                                       if (!data.checkboxes[i].checked) {
-                                               markedAll = false;
-                                               
-                                               break;
-                                       }
-                               }
-                               
-                               data.markAll.checked = markedAll;
-                               
-                               if (elAttr(data.markAll.parentNode, 'role') === 'checkbox') {
-                                       elAttr(data.markAll.parentNode, 'aria-checked', isMarked);
-                               }
-                       }
-                       
-                       if (elAttr(checkbox.parentNode, 'role') === 'checkbox') {
-                               elAttr(checkbox.parentNode, 'aria-checked', checkbox.checked);
-                       }
-                       
-                       this._saveState(type, [ objectId ], isMarked);
-               },
-               
-               /**
-                * Saves the state for given item object ids.
-                * 
-                * @param       {string}        type            object type
-                * @param       {int[]}         objectIds       item object ids
-                * @param       {boolean}       isMarked        true if marked
-                */
-               _saveState: function(type, objectIds, isMarked) {
-                       Ajax.api(this, {
-                               actionName: (isMarked ? 'mark' : 'unmark'),
-                               parameters: {
-                                       pageClassNames: _options.pageClassNames,
-                                       pageObjectID: _options.pageObjectId,
-                                       objectIDs: objectIds,
-                                       objectType: type
-                               }
-                       });
-               },
-               
-               /**
-                * Executes an editor action.
-                * 
-                * @param       {object}        event           event object
-                */
-               _executeAction: function(event) {
-                       var listItem = event.currentTarget;
-                       var data = _itemData.get(listItem);
-                       
-                       if (data.url) {
-                               window.location.href = data.url;
-                               return;
-                       }
-                       
-                       var triggerEvent = function() {
-                               var type = elData(listItem, 'type');
-                               
-                               EventHandler.fire('com.woltlab.wcf.clipboard', type, {
-                                       data: data,
-                                       listItem: listItem,
-                                       responseData: null
-                               });
-                       };
-                       
-                       //noinspection JSUnresolvedVariable
-                       var confirmMessage = (typeof data.internalData.confirmMessage === 'string') ? data.internalData.confirmMessage : '';
-                       var fireEvent = true;
-                       
-                       if (typeof data.parameters === 'object' && data.parameters.actionName && data.parameters.className) {
-                               if (data.parameters.actionName === 'unmarkAll' || Array.isArray(data.parameters.objectIDs)) {
-                                       if (confirmMessage.length) {
-                                               //noinspection JSUnresolvedVariable
-                                               var template = (typeof data.internalData.template === 'string') ? data.internalData.template : '';
-                                               
-                                               UiConfirmation.show({
-                                                       confirm: (function() {
-                                                               var formData = {};
-                                                               
-                                                               if (template.length) {
-                                                                       var items = elBySelAll('input, select, textarea', UiConfirmation.getContentElement());
-                                                                       for (var i = 0, length = items.length; i < length; i++) {
-                                                                               var item = items[i];
-                                                                               var name = elAttr(item, 'name');
-                                                                               
-                                                                               switch (item.nodeName) {
-                                                                                       case 'INPUT':
-                                                                                               if ((item.type !== "checkbox" && item.type !== "radio") || item.checked) {
-                                                                                                       formData[name] = elAttr(item, 'value');
-                                                                                               }
-                                                                                               break;
-                                                                                       
-                                                                                       case 'SELECT':
-                                                                                               formData[name] = item.value;
-                                                                                               break;
-                                                                                       
-                                                                                       case 'TEXTAREA':
-                                                                                               formData[name] = item.value.trim();
-                                                                                               break;
-                                                                               }
-                                                                       }
-                                                               }
-                                                               
-                                                               //noinspection JSUnresolvedFunction
-                                                               this._executeProxyAction(listItem, data, formData);
-                                                       }).bind(this),
-                                                       message: confirmMessage,
-                                                       template: template
-                                               });
-                                       }
-                                       else {
-                                               this._executeProxyAction(listItem, data);
-                                       }
-                               }
-                       }
-                       else if (confirmMessage.length) {
-                               fireEvent = false;
-                               
-                               UiConfirmation.show({
-                                       confirm: triggerEvent,
-                                       message: confirmMessage
-                               });
-                       }
-                       
-                       if (fireEvent) {
-                               triggerEvent();
-                       }
-               },
-               
-               /**
-                * Forwards clipboard actions to an individual handler.
-                * 
-                * @param       {Element}       listItem        dropdown item element
-                * @param       {Object}        data            action data
-                * @param       {Object?}       formData        form data
-                */
-               _executeProxyAction: function(listItem, data, formData) {
-                       formData = formData || {};
-                       
-                       var objectIds = (data.parameters.actionName !== 'unmarkAll') ? data.parameters.objectIDs : [];
-                       var parameters = { data: formData };
-                       
-                       //noinspection JSUnresolvedVariable
-                       if (typeof data.internalData.parameters === 'object') {
-                               //noinspection JSUnresolvedVariable
-                               for (var key in data.internalData.parameters) {
-                                       //noinspection JSUnresolvedVariable
-                                       if (data.internalData.parameters.hasOwnProperty(key)) {
-                                               //noinspection JSUnresolvedVariable
-                                               parameters[key] = data.internalData.parameters[key];
-                                       }
-                               }
-                       }
-                       
-                       Ajax.api(this, {
-                               actionName: data.parameters.actionName,
-                               className: data.parameters.className,
-                               objectIDs: objectIds,
-                               parameters: parameters
-                       }, (function(responseData) {
-                               if (data.actionName !== 'unmarkAll') {
-                                       var type = elData(listItem, 'type');
-                                       
-                                       EventHandler.fire('com.woltlab.wcf.clipboard', type, {
-                                               data: data,
-                                               listItem: listItem,
-                                               responseData: responseData
-                                       });
-                                       
-                                       if (_reloadPageOnSuccess.has(type) && _reloadPageOnSuccess.get(type).indexOf(responseData.actionName) !== -1) {
-                                               window.location.reload();
-                                               return;
-                                       }
-                               }
-                               
-                               this._loadMarkedItems();
-                       }).bind(this));
-               },
-               
-               /**
-                * Unmarks all clipboard items for an object type.
-                * 
-                * @param       {object}        event           event object
-                */
-               _unmarkAll: function(event) {
-                       var type = elData(event.currentTarget, 'type');
-                       
-                       Ajax.api(this, {
-                               actionName: 'unmarkAll',
-                               parameters: {
-                                       objectType: type
-                               }
-                       });
-               },
-               
-               /**
-                * Sets up ajax request object.
-                * 
-                * @return      {object}        request options
-                */
-               _ajaxSetup: function() {
-                       return {
-                               data: {
-                                       className: 'wcf\\data\\clipboard\\item\\ClipboardItemAction'
-                               }
-                       };
-               },
-               
-               /**
-                * Handles successful AJAX requests.
-                * 
-                * @param       {object}        data    response data
-                */
-               _ajaxSuccess: function(data) {
-                       if (data.actionName === 'unmarkAll') {
-                               _containers.forEach((function(containerData) {
-                                       if (elData(containerData.element, 'type') === data.returnValues.objectType) {
-                                               var clipboardObjects = elByClass('jsMarked', containerData.element);
-                                               while (clipboardObjects.length) {
-                                                       clipboardObjects[0].classList.remove('jsMarked');
-                                               }
-                                               
-                                               if (containerData.markAll !== null) {
-                                                       containerData.markAll.checked = false;
-                                                       
-                                                       if (elAttr(containerData.markAll.parentNode, 'role') === 'checkbox') {
-                                                               elAttr(containerData.markAll.parentNode, 'aria-checked', false);
-                                                       }
-                                               }
-                                               for (var i = 0, length = containerData.checkboxes.length; i < length; i++) {
-                                                       containerData.checkboxes[i].checked = false;
-                                                       
-                                                       if (elAttr(containerData.checkboxes[i].parentNode, 'role') === 'checkbox') {
-                                                               elAttr(containerData.checkboxes[i].parentNode, 'aria-checked', false);
-                                                       }
-                                               }
-                                               
-                                               UiPageAction.remove('wcfClipboard-' + data.returnValues.objectType);
-                                       }
-                               }).bind(this));
-                               
-                               return;
-                       }
-                       
-                       _itemData = new ObjectMap();
-                       _reloadPageOnSuccess = new Dictionary();
-                       
-                       // rebuild markings
-                       _containers.forEach((function(containerData) {
-                               var typeName = elData(containerData.element, 'type');
-                               
-                               //noinspection JSUnresolvedVariable
-                               var objectIds = (data.returnValues.markedItems && data.returnValues.markedItems.hasOwnProperty(typeName)) ? data.returnValues.markedItems[typeName] : [];
-                               this._rebuildMarkings(containerData, objectIds);
-                       }).bind(this));
-                       
-                       var keepEditors = [], typeName;
-                       if (data.returnValues && data.returnValues.items) {
-                               for (typeName in data.returnValues.items) {
-                                       if (data.returnValues.items.hasOwnProperty(typeName)) {
-                                               keepEditors.push(typeName);
-                                       }
-                               }
-                       }
-                       
-                       // clear editors
-                       _editors.forEach(function(editor, typeName) {
-                               if (keepEditors.indexOf(typeName) === -1) {
-                                       UiPageAction.remove('wcfClipboard-' + typeName);
-                                       
-                                       _editorDropdowns.get(typeName).innerHTML = '';
-                               }
-                       });
-                       
-                       // no items
-                       if (!data.returnValues || !data.returnValues.items) {
-                               return;
-                       }
-                       
-                       // rebuild editors
-                       var actionName, created, dropdown, editor, typeData;
-                       var divider, item, itemData, itemIndex, label, unmarkAll;
-                       for (typeName in data.returnValues.items) {
-                               if (!data.returnValues.items.hasOwnProperty(typeName)) {
-                                       continue;
-                               }
-                               
-                               typeData = data.returnValues.items[typeName];
-                               //noinspection JSUnresolvedVariable
-                               _reloadPageOnSuccess.set(typeName, typeData.reloadPageOnSuccess);
-                               created = false;
-                               
-                               editor = _editors.get(typeName);
-                               dropdown = _editorDropdowns.get(typeName);
-                               if (editor === undefined) {
-                                       created = true;
-                                       
-                                       editor = elCreate('a');
-                                       editor.className = 'dropdownToggle';
-                                       editor.textContent = typeData.label;
-                                       
-                                       _editors.set(typeName, editor);
-                                       
-                                       dropdown = elCreate('ol');
-                                       dropdown.className = 'dropdownMenu';
-                                       
-                                       _editorDropdowns.set(typeName, dropdown);
-                               }
-                               else {
-                                       editor.textContent = typeData.label;
-                                       dropdown.innerHTML = '';
-                               }
-                               
-                               // create editor items
-                               for (itemIndex in typeData.items) {
-                                       if (!typeData.items.hasOwnProperty(itemIndex)) {
-                                               continue;
-                                       }
-                                       
-                                       itemData = typeData.items[itemIndex];
-                                       
-                                       item = elCreate('li');
-                                       label = elCreate('span');
-                                       label.textContent = itemData.label;
-                                       item.appendChild(label);
-                                       dropdown.appendChild(item);
-                                       
-                                       elData(item, 'type', typeName);
-                                       item.addEventListener(WCF_CLICK_EVENT, _callbackItem);
-                                       
-                                       _itemData.set(item, itemData);
-                               }
-                               
-                               divider = elCreate('li');
-                               divider.classList.add('dropdownDivider');
-                               dropdown.appendChild(divider);
-                               
-                               // add 'unmark all'
-                               unmarkAll = elCreate('li');
-                               elData(unmarkAll, 'type', typeName);
-                               label = elCreate('span');
-                               label.textContent = Language.get('wcf.clipboard.item.unmarkAll');
-                               unmarkAll.appendChild(label);
-                               unmarkAll.addEventListener(WCF_CLICK_EVENT, _callbackUnmarkAll);
-                               dropdown.appendChild(unmarkAll);
-                               
-                               if (keepEditors.indexOf(typeName) !== -1) {
-                                       actionName = 'wcfClipboard-' + typeName;
-                                       
-                                       if (UiPageAction.has(actionName)) {
-                                               UiPageAction.show(actionName);
-                                       }
-                                       else {
-                                               UiPageAction.add(actionName, editor);
-                                       }
-                               }
-                               
-                               if (created) {
-                                       editor.parentNode.classList.add('dropdown');
-                                       editor.parentNode.appendChild(dropdown);
-                                       UiSimpleDropdown.init(editor);
-                               }
-                       }
-               },
-               
-               /**
-                * Rebuilds the mark state for each item.
-                * 
-                * @param       {Object}        data            container data
-                * @param       {int[]}         objectIds       item object ids
-                */
-               _rebuildMarkings: function(data, objectIds) {
-                       var markAll = true;
-                       
-                       for (var i = 0, length = data.checkboxes.length; i < length; i++) {
-                               var checkbox = data.checkboxes[i];
-                               var clipboardObject = DomTraverse.parentByClass(checkbox, 'jsClipboardObject');
-                               
-                               var isMarked = (objectIds.indexOf(~~elData(checkbox, 'object-id')) !== -1);
-                               if (!isMarked) markAll = false;
-                               
-                               checkbox.checked = isMarked;
-                               clipboardObject.classList[(isMarked ? 'add' : 'remove')]('jsMarked');
-                               
-                               if (elAttr(checkbox.parentNode, 'role') === 'checkbox') {
-                                       elAttr(checkbox.parentNode, 'aria-checked', isMarked);
-                               }
-                       }
-                       
-                       if (data.markAll !== null) {
-                               data.markAll.checked = markAll;
-                               
-                               if (elAttr(data.markAll.parentNode, 'role') === 'checkbox') {
-                                       elAttr(data.markAll.parentNode, 'aria-checked', markAll);
-                               }
-                               
-                               var parent = data.markAll;
-                               while (parent = parent.parentNode) {
-                                       if (parent instanceof Element && parent.classList.contains('columnMark')) {
-                                               parent = parent.parentNode;
-                                               break;
-                                       }
-                               }
-                               
-                               if (parent) {
-                                       parent.classList[(markAll ? 'add' : 'remove')]('jsMarked');
-                               }
-                       }
-               },
-               
-               /**
-                * Hides the clipboard editor for the given object type.
-                * 
-                * @param       {string}        objectType
-                */
-               hideEditor: function(objectType) {
-                       UiPageAction.remove('wcfClipboard-' + objectType);
-                       
-                       UiScreen.pageOverlayOpen();
-               },
-               
-               /**
-                * Shows the clipboard editor.
-                */
-               showEditor: function() {
-                       this._loadMarkedItems();
-                       
-                       UiScreen.pageOverlayClose();
-               },
-               
-               /**
-                * Unmarks the objects with given clipboard object type and ids.
-                * 
-                * @param       {string}        objectType
-                * @param       {int[]}         objectIds
-                */
-               unmark: function(objectType, objectIds) {
-                       this._saveState(objectType, objectIds, false);
-               }
-       };
-});
-
-/**
- * Provides helper functions for Exif metadata handling.
- *
- * @author     Maximilian Mader
- * @copyright  2001-2018 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Image/ExifUtil
- */
-define('WoltLabSuite/Core/Image/ExifUtil',[], function() {
-       "use strict";
-       
-       var _tagNames = {
-               'SOI':   0xD8, // Start of image
-               'APP0':  0xE0, // JFIF tag
-               'APP1':  0xE1, // EXIF / XMP
-               'APP2':  0xE2, // General purpose tag
-               'APP3':  0xE3, // General purpose tag
-               'APP4':  0xE4, // General purpose tag
-               'APP5':  0xE5, // General purpose tag
-               'APP6':  0xE6, // General purpose tag
-               'APP7':  0xE7, // General purpose tag
-               'APP8':  0xE8, // General purpose tag
-               'APP9':  0xE9, // General purpose tag
-               'APP10': 0xEA, // General purpose tag
-               'APP11': 0xEB, // General purpose tag
-               'APP12': 0xEC, // General purpose tag
-               'APP13': 0xED, // General purpose tag
-               'APP14': 0xEE, // Often used to store copyright information
-               'COM':   0xFE, // Comments
-       };
-       
-       // Known sequence signatures
-       var _signatureEXIF = 'Exif';
-       var _signatureXMP  = 'http://ns.adobe.com/xap/1.0/';
-       
-       return {
-               /**
-                * Extracts the EXIF / XMP sections of a JPEG blob.
-                *
-                * @param       blob    {Blob}                                  JPEG blob
-                * @returns             {Promise<Uint8Array | TypeError>}       Promise resolving with the EXIF / XMP sections
-                */
-               getExifBytesFromJpeg: function (blob) {
-                       return new Promise(function (resolve, reject) {
-                               if (!(blob instanceof Blob) && !(blob instanceof File)) {
-                                       return reject(new TypeError('The argument must be a Blob or a File'));
-                               }
-                               
-                               var reader = new FileReader();
-                               
-                               reader.addEventListener('error', function () {
-                                       reader.abort();
-                                       reject(reader.error);
-                               });
-                               
-                               reader.addEventListener('load', function() {
-                                       var buffer = reader.result;
-                                       var bytes = new Uint8Array(buffer);
-                                       var exif = new Uint8Array();
-                                       
-                                       if (bytes[0] !== 0xFF && bytes[1] !== _tagNames.SOI) {
-                                               return reject(new Error('Not a JPEG'));
-                                       }
-                                       
-                                       for (var i = 2; i < bytes.length;) {
-                                               // each sequence starts with 0xFF
-                                               if (bytes[i] !== 0xFF) break;
-                                               
-                                               var length = 2 + ((bytes[i + 2] << 8) | bytes[i + 3]);
-                                               
-                                               // Check if the next byte indicates an EXIF sequence
-                                               if (bytes[i + 1] === _tagNames.APP1) {
-                                                       var signature = '';
-                                                       for (var j = i + 4; bytes[j] !== 0 && j < bytes.length; j++) {
-                                                               signature += String.fromCharCode(bytes[j]);
-                                                       }
-                                                       
-                                                       // Only copy Exif and XMP data
-                                                       if (signature === _signatureEXIF || signature === _signatureXMP) {
-                                                               // append the found EXIF sequence, usually only a single EXIF (APP1) sequence should be defined
-                                                               var sequence = Array.prototype.slice.call(bytes, i, length + i); // IE11 does not have slice in the Uint8Array prototype
-                                                               var concat = new Uint8Array(exif.length + sequence.length);
-                                                               concat.set(exif);
-                                                               concat.set(sequence, exif.length);
-                                                               exif = concat;
-                                                       }
-                                               }
-                                               
-                                               i += length
-                                       }
-                                       
-                                       // No EXIF data found
-                                       resolve(exif);
-                               });
-                               
-                               reader.readAsArrayBuffer(blob);
-                       });
-               },
-               
-               /**
-                * Removes all EXIF and XMP sections of a JPEG blob.
-                *
-                * @param       blob    {Blob}                          JPEG blob
-                * @returns             {Promise<Blob | TypeError>}     Promise resolving with the altered JPEG blob
-                */
-               removeExifData: function (blob) {
-                       return new Promise(function (resolve, reject) {
-                               if (!(blob instanceof Blob) && !(blob instanceof File)) {
-                                       return reject(new TypeError('The argument must be a Blob or a File'));
-                               }
-                               
-                               var reader = new FileReader();
-                               
-                               reader.addEventListener('error', function () {
-                                       reader.abort();
-                                       reject(reader.error);
-                               });
-                               
-                               reader.addEventListener('load', function () {
-                                       var buffer = reader.result;
-                                       var bytes = new Uint8Array(buffer);
-                                       
-                                       if (bytes[0] !== 0xFF && bytes[1] !== _tagNames.SOI) {
-                                               return reject(new Error('Not a JPEG'));
-                                       }
-                                       
-                                       for (var i = 2; i < bytes.length;) {
-                                               // each sequence starts with 0xFF
-                                               if (bytes[i] !== 0xFF) break;
-                                               
-                                               var length = 2 + ((bytes[i + 2] << 8) | bytes[i + 3]);
-                                               
-                                               // Check if the next byte indicates an EXIF sequence
-                                               if (bytes[i + 1] === _tagNames.APP1) {
-                                                       var signature = '';
-                                                       for (var j = i + 4; bytes[j] !== 0 && j < bytes.length; j++) {
-                                                               signature += String.fromCharCode(bytes[j]);
-                                                       }
-                                                       
-                                                       // Only remove Exif and XMP data
-                                                       if (signature === _signatureEXIF || signature === _signatureXMP) {
-                                                               var start = Array.prototype.slice.call(bytes, 0, i);
-                                                               var end = Array.prototype.slice.call(bytes, i + length);
-                                                               bytes = new Uint8Array(start.length + end.length);
-                                                               bytes.set(start, 0);
-                                                               bytes.set(end, start.length);
-                                                       }
-                                               }
-                                               else {
-                                                       i += length;
-                                               }
-                                       }
-                                       
-                                       resolve(new Blob([bytes], {type: blob.type}));
-                               });
-                               
-                               reader.readAsArrayBuffer(blob);
-                       });
-               },
-               
-               /**
-                * Overrides the APP1 (EXIF / XMP) sections of a JPEG blob with the given data.
-                *
-                * @param       blob    {Blob}                  JPEG blob
-                * @param       exif    {Uint8Array}            APP1 sections
-                * @returns             {Promise<Blob | never>} Promise resolving with the altered JPEG blob
-                */
-               setExifData: function (blob, exif) {
-                       return this.removeExifData(blob).then(function (blob) {
-                               return new Promise(function (resolve) {
-                                       var reader = new FileReader();
-                                       
-                                       reader.addEventListener('error', function () {
-                                               reader.abort();
-                                               reject(reader.error);
-                                       });
-                                       
-                                       reader.addEventListener('load', function () {
-                                               var buffer = reader.result;
-                                               var bytes = new Uint8Array(buffer);
-                                               var offset = 2;
-                                               
-                                               // check if the second tag is the JFIF tag
-                                               if (bytes[2] === 0xFF && bytes[3] === _tagNames.APP0) {
-                                                       offset += 2 + ((bytes[4] << 8) | bytes[5]);
-                                               }
-                                               
-                                               var start = Array.prototype.slice.call(bytes, 0, offset);
-                                               var end = Array.prototype.slice.call(bytes, offset);
-                                               
-                                               bytes = new Uint8Array(start.length + exif.length + end.length);
-                                               bytes.set(start);
-                                               bytes.set(exif, offset);
-                                               bytes.set(end, offset + exif.length);
-                                               
-                                               resolve(new Blob([bytes], {type: blob.type}));
-                                       });
-                                       
-                                       reader.readAsArrayBuffer(blob);
-                               });
-                       });
-               }
-       };
-});
-
-/**
- * Provides helper functions for Image metadata 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/Image/ImageUtil
- */
-define('WoltLabSuite/Core/Image/ImageUtil',[], function() {
-       "use strict";
-       
-       return {
-               /**
-                * Returns whether the given canvas contains transparent pixels.
-                *
-                * @param       image   {Canvas}  Canvas to check
-                * @returns             {bool}
-                */
-               containsTransparentPixels: function (canvas) {
-                       var imageData = canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height);
-                       
-                       for (var i = 3, max = imageData.data.length; i < max; i += 4) {
-                               if (imageData.data[i] !== 255) return true;
-                       }
-                       
-                       return false;
-               }
-       };
-});
-
-/* (The MIT License)
-
-Copyright (C) 2014-2017 by Vitaly Puzrin
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE. */
-/* pica 5.0.0 nodeca/pica */(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define('Pica',[],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.pica = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
-// Collection of math functions
-//
-// 1. Combine components together
-// 2. Has async init to load wasm modules
-//
-               'use strict';
-               
-               var inherits = require('inherits');
-               
-               var Multimath = require('multimath');
-               
-               var mm_unsharp_mask = require('multimath/lib/unsharp_mask');
-               
-               var mm_resize = require('./mm_resize');
-               
-               function MathLib(requested_features) {
-                       var __requested_features = requested_features || [];
-                       
-                       var features = {
-                               js: __requested_features.indexOf('js') >= 0,
-                               wasm: __requested_features.indexOf('wasm') >= 0
-                       };
-                       Multimath.call(this, features);
-                       this.features = {
-                               js: features.js,
-                               wasm: features.wasm && this.has_wasm
-                       };
-                       this.use(mm_unsharp_mask);
-                       this.use(mm_resize);
-               }
-               
-               inherits(MathLib, Multimath);
-               
-               MathLib.prototype.resizeAndUnsharp = function resizeAndUnsharp(options, cache) {
-                       var result = this.resize(options, cache);
-                       
-                       if (options.unsharpAmount) {
-                               this.unsharp_mask(result, options.toWidth, options.toHeight, options.unsharpAmount, options.unsharpRadius, options.unsharpThreshold);
-                       }
-                       
-                       return result;
-               };
-               
-               module.exports = MathLib;
-               
-       },{"./mm_resize":4,"inherits":15,"multimath":16,"multimath/lib/unsharp_mask":19}],2:[function(require,module,exports){
-// Resize convolvers, pure JS implementation
-//
-               'use strict'; // Precision of fixed FP values
-//var FIXED_FRAC_BITS = 14;
-               
-               function clampTo8(i) {
-                       return i < 0 ? 0 : i > 255 ? 255 : i;
-               } // Convolve image in horizontal directions and transpose output. In theory,
-// transpose allow:
-//
-// - use the same convolver for both passes (this fails due different
-//   types of input array and temporary buffer)
-// - making vertical pass by horisonltal lines inprove CPU cache use.
-//
-// But in real life this doesn't work :)
-//
-               
-               
-               function convolveHorizontally(src, dest, srcW, srcH, destW, filters) {
-                       var r, g, b, a;
-                       var filterPtr, filterShift, filterSize;
-                       var srcPtr, srcY, destX, filterVal;
-                       var srcOffset = 0,
-                               destOffset = 0; // For each row
-                       
-                       for (srcY = 0; srcY < srcH; srcY++) {
-                               filterPtr = 0; // Apply precomputed filters to each destination row point
-                               
-                               for (destX = 0; destX < destW; destX++) {
-                                       // Get the filter that determines the current output pixel.
-                                       filterShift = filters[filterPtr++];
-                                       filterSize = filters[filterPtr++];
-                                       srcPtr = srcOffset + filterShift * 4 | 0;
-                                       r = g = b = a = 0; // Apply the filter to the row to get the destination pixel r, g, b, a
-                                       
-                                       for (; filterSize > 0; filterSize--) {
-                                               filterVal = filters[filterPtr++]; // Use reverse order to workaround deopts in old v8 (node v.10)
-                                               // Big thanks to @mraleph (Vyacheslav Egorov) for the tip.
-                                               
-                                               a = a + filterVal * src[srcPtr + 3] | 0;
-                                               b = b + filterVal * src[srcPtr + 2] | 0;
-                                               g = g + filterVal * src[srcPtr + 1] | 0;
-                                               r = r + filterVal * src[srcPtr] | 0;
-                                               srcPtr = srcPtr + 4 | 0;
-                                       } // Bring this value back in range. All of the filter scaling factors
-                                       // are in fixed point with FIXED_FRAC_BITS bits of fractional part.
-                                       //
-                                       // (!) Add 1/2 of value before clamping to get proper rounding. In other
-                                       // case brightness loss will be noticeable if you resize image with white
-                                       // border and place it on white background.
-                                       //
-                                       
-                                       
-                                       dest[destOffset + 3] = clampTo8(a + (1 << 13) >> 14
-                                               /*FIXED_FRAC_BITS*/
-                                       );
-                                       dest[destOffset + 2] = clampTo8(b + (1 << 13) >> 14
-                                               /*FIXED_FRAC_BITS*/
-                                       );
-                                       dest[destOffset + 1] = clampTo8(g + (1 << 13) >> 14
-                                               /*FIXED_FRAC_BITS*/
-                                       );
-                                       dest[destOffset] = clampTo8(r + (1 << 13) >> 14
-                                               /*FIXED_FRAC_BITS*/
-                                       );
-                                       destOffset = destOffset + srcH * 4 | 0;
-                               }
-                               
-                               destOffset = (srcY + 1) * 4 | 0;
-                               srcOffset = (srcY + 1) * srcW * 4 | 0;
-                       }
-               } // Technically, convolvers are the same. But input array and temporary
-// buffer can be of different type (especially, in old browsers). So,
-// keep code in separate functions to avoid deoptimizations & speed loss.
-               
-               
-               function convolveVertically(src, dest, srcW, srcH, destW, filters) {
-                       var r, g, b, a;
-                       var filterPtr, filterShift, filterSize;
-                       var srcPtr, srcY, destX, filterVal;
-                       var srcOffset = 0,
-                               destOffset = 0; // For each row
-                       
-                       for (srcY = 0; srcY < srcH; srcY++) {
-                               filterPtr = 0; // Apply precomputed filters to each destination row point
-                               
-                               for (destX = 0; destX < destW; destX++) {
-                                       // Get the filter that determines the current output pixel.
-                                       filterShift = filters[filterPtr++];
-                                       filterSize = filters[filterPtr++];
-                                       srcPtr = srcOffset + filterShift * 4 | 0;
-                                       r = g = b = a = 0; // Apply the filter to the row to get the destination pixel r, g, b, a
-                                       
-                                       for (; filterSize > 0; filterSize--) {
-                                               filterVal = filters[filterPtr++]; // Use reverse order to workaround deopts in old v8 (node v.10)
-                                               // Big thanks to @mraleph (Vyacheslav Egorov) for the tip.
-                                               
-                                               a = a + filterVal * src[srcPtr + 3] | 0;
-                                               b = b + filterVal * src[srcPtr + 2] | 0;
-                                               g = g + filterVal * src[srcPtr + 1] | 0;
-                                               r = r + filterVal * src[srcPtr] | 0;
-                                               srcPtr = srcPtr + 4 | 0;
-                                       } // Bring this value back in range. All of the filter scaling factors
-                                       // are in fixed point with FIXED_FRAC_BITS bits of fractional part.
-                                       //
-                                       // (!) Add 1/2 of value before clamping to get proper rounding. In other
-                                       // case brightness loss will be noticeable if you resize image with white
-                                       // border and place it on white background.
-                                       //
-                                       
-                                       
-                                       dest[destOffset + 3] = clampTo8(a + (1 << 13) >> 14
-                                               /*FIXED_FRAC_BITS*/
-                                       );
-                                       dest[destOffset + 2] = clampTo8(b + (1 << 13) >> 14
-                                               /*FIXED_FRAC_BITS*/
-                                       );
-                                       dest[destOffset + 1] = clampTo8(g + (1 << 13) >> 14
-                                               /*FIXED_FRAC_BITS*/
-                                       );
-                                       dest[destOffset] = clampTo8(r + (1 << 13) >> 14
-                                               /*FIXED_FRAC_BITS*/
-                                       );
-                                       destOffset = destOffset + srcH * 4 | 0;
-                               }
-                               
-                               destOffset = (srcY + 1) * 4 | 0;
-                               srcOffset = (srcY + 1) * srcW * 4 | 0;
-                       }
-               }
-               
-               module.exports = {
-                       convolveHorizontally: convolveHorizontally,
-                       convolveVertically: convolveVertically
-               };
-               
-       },{}],3:[function(require,module,exports){
-// This is autogenerated file from math.wasm, don't edit.
-//
-               'use strict';
-               /* eslint-disable max-len */
-               
-               module.exports = 'AGFzbQEAAAABFAJgBn9/f39/fwBgB39/f39/f38AAg8BA2VudgZtZW1vcnkCAAEDAwIAAQQEAXAAAAcZAghjb252b2x2ZQAACmNvbnZvbHZlSFYAAQkBAArmAwLBAwEQfwJAIANFDQAgBEUNACAFQQRqIRVBACEMQQAhDQNAIA0hDkEAIRFBACEHA0AgB0ECaiESAn8gBSAHQQF0IgdqIgZBAmouAQAiEwRAQQAhCEEAIBNrIRQgFSAHaiEPIAAgDCAGLgEAakECdGohEEEAIQlBACEKQQAhCwNAIBAoAgAiB0EYdiAPLgEAIgZsIAtqIQsgB0H/AXEgBmwgCGohCCAHQRB2Qf8BcSAGbCAKaiEKIAdBCHZB/wFxIAZsIAlqIQkgD0ECaiEPIBBBBGohECAUQQFqIhQNAAsgEiATagwBC0EAIQtBACEKQQAhCUEAIQggEgshByABIA5BAnRqIApBgMAAakEOdSIGQf8BIAZB/wFIG0EQdEGAgPwHcUEAIAZBAEobIAtBgMAAakEOdSIGQf8BIAZB/wFIG0EYdEEAIAZBAEobciAJQYDAAGpBDnUiBkH/ASAGQf8BSBtBCHRBgP4DcUEAIAZBAEobciAIQYDAAGpBDnUiBkH/ASAGQf8BSBtB/wFxQQAgBkEAShtyNgIAIA4gA2ohDiARQQFqIhEgBEcNAAsgDCACaiEMIA1BAWoiDSADRw0ACwsLIQACQEEAIAIgAyAEIAUgABAAIAJBACAEIAUgBiABEAALCw==';
-               
-       },{}],4:[function(require,module,exports){
-               'use strict';
-               
-               module.exports = {
-                       name: 'resize',
-                       fn: require('./resize'),
-                       wasm_fn: require('./resize_wasm'),
-                       wasm_src: require('./convolve_wasm_base64')
-               };
-               
-       },{"./convolve_wasm_base64":3,"./resize":5,"./resize_wasm":8}],5:[function(require,module,exports){
-               'use strict';
-               
-               var createFilters = require('./resize_filter_gen');
-               
-               var convolveHorizontally = require('./convolve').convolveHorizontally;
-               
-               var convolveVertically = require('./convolve').convolveVertically;
-               
-               function resetAlpha(dst, width, height) {
-                       var ptr = 3,
-                               len = width * height * 4 | 0;
-                       
-                       while (ptr < len) {
-                               dst[ptr] = 0xFF;
-                               ptr = ptr + 4 | 0;
-                       }
-               }
-               
-               module.exports = function resize(options) {
-                       var src = options.src;
-                       var srcW = options.width;
-                       var srcH = options.height;
-                       var destW = options.toWidth;
-                       var destH = options.toHeight;
-                       var scaleX = options.scaleX || options.toWidth / options.width;
-                       var scaleY = options.scaleY || options.toHeight / options.height;
-                       var offsetX = options.offsetX || 0;
-                       var offsetY = options.offsetY || 0;
-                       var dest = options.dest || new Uint8Array(destW * destH * 4);
-                       var quality = typeof options.quality === 'undefined' ? 3 : options.quality;
-                       var alpha = options.alpha || false;
-                       var filtersX = createFilters(quality, srcW, destW, scaleX, offsetX),
-                               filtersY = createFilters(quality, srcH, destH, scaleY, offsetY);
-                       var tmp = new Uint8Array(destW * srcH * 4); // To use single function we need src & tmp of the same type.
-                       // But src can be CanvasPixelArray, and tmp - Uint8Array. So, keep
-                       // vertical and horizontal passes separately to avoid deoptimization.
-                       
-                       convolveHorizontally(src, tmp, srcW, srcH, destW, filtersX);
-                       convolveVertically(tmp, dest, srcH, destW, destH, filtersY); // That's faster than doing checks in convolver.
-                       // !!! Note, canvas data is not premultipled. We don't need other
-                       // alpha corrections.
-                       
-                       if (!alpha) resetAlpha(dest, destW, destH);
-                       return dest;
-               };
-               
-       },{"./convolve":2,"./resize_filter_gen":6}],6:[function(require,module,exports){
-// Calculate convolution filters for each destination point,
-// and pack data to Int16Array:
-//
-// [ shift, length, data..., shift2, length2, data..., ... ]
-//
-// - shift - offset in src image
-// - length - filter length (in src points)
-// - data - filter values sequence
-//
-               'use strict';
-               
-               var FILTER_INFO = require('./resize_filter_info'); // Precision of fixed FP values
-               
-               
-               var FIXED_FRAC_BITS = 14;
-               
-               function toFixedPoint(num) {
-                       return Math.round(num * ((1 << FIXED_FRAC_BITS) - 1));
-               }
-               
-               module.exports = function resizeFilterGen(quality, srcSize, destSize, scale, offset) {
-                       var filterFunction = FILTER_INFO[quality].filter;
-                       var scaleInverted = 1.0 / scale;
-                       var scaleClamped = Math.min(1.0, scale); // For upscale
-                       // Filter window (averaging interval), scaled to src image
-                       
-                       var srcWindow = FILTER_INFO[quality].win / scaleClamped;
-                       var destPixel, srcPixel, srcFirst, srcLast, filterElementSize, floatFilter, fxpFilter, total, pxl, idx, floatVal, filterTotal, filterVal;
-                       var leftNotEmpty, rightNotEmpty, filterShift, filterSize;
-                       var maxFilterElementSize = Math.floor((srcWindow + 1) * 2);
-                       var packedFilter = new Int16Array((maxFilterElementSize + 2) * destSize);
-                       var packedFilterPtr = 0;
-                       var slowCopy = !packedFilter.subarray || !packedFilter.set; // For each destination pixel calculate source range and built filter values
-                       
-                       for (destPixel = 0; destPixel < destSize; destPixel++) {
-                               // Scaling should be done relative to central pixel point
-                               srcPixel = (destPixel + 0.5) * scaleInverted + offset;
-                               srcFirst = Math.max(0, Math.floor(srcPixel - srcWindow));
-                               srcLast = Math.min(srcSize - 1, Math.ceil(srcPixel + srcWindow));
-                               filterElementSize = srcLast - srcFirst + 1;
-                               floatFilter = new Float32Array(filterElementSize);
-                               fxpFilter = new Int16Array(filterElementSize);
-                               total = 0.0; // Fill filter values for calculated range
-                               
-                               for (pxl = srcFirst, idx = 0; pxl <= srcLast; pxl++, idx++) {
-                                       floatVal = filterFunction((pxl + 0.5 - srcPixel) * scaleClamped);
-                                       total += floatVal;
-                                       floatFilter[idx] = floatVal;
-                               } // Normalize filter, convert to fixed point and accumulate conversion error
-                               
-                               
-                               filterTotal = 0;
-                               
-                               for (idx = 0; idx < floatFilter.length; idx++) {
-                                       filterVal = floatFilter[idx] / total;
-                                       filterTotal += filterVal;
-                                       fxpFilter[idx] = toFixedPoint(filterVal);
-                               } // Compensate normalization error, to minimize brightness drift
-                               
-                               
-                               fxpFilter[destSize >> 1] += toFixedPoint(1.0 - filterTotal); //
-                               // Now pack filter to useable form
-                               //
-                               // 1. Trim heading and tailing zero values, and compensate shitf/length
-                               // 2. Put all to single array in this format:
-                               //
-                               //    [ pos shift, data length, value1, value2, value3, ... ]
-                               //
-                               
-                               leftNotEmpty = 0;
-                               
-                               while (leftNotEmpty < fxpFilter.length && fxpFilter[leftNotEmpty] === 0) {
-                                       leftNotEmpty++;
-                               }
-                               
-                               if (leftNotEmpty < fxpFilter.length) {
-                                       rightNotEmpty = fxpFilter.length - 1;
-                                       
-                                       while (rightNotEmpty > 0 && fxpFilter[rightNotEmpty] === 0) {
-                                               rightNotEmpty--;
-                                       }
-                                       
-                                       filterShift = srcFirst + leftNotEmpty;
-                                       filterSize = rightNotEmpty - leftNotEmpty + 1;
-                                       packedFilter[packedFilterPtr++] = filterShift; // shift
-                                       
-                                       packedFilter[packedFilterPtr++] = filterSize; // size
-                                       
-                                       if (!slowCopy) {
-                                               packedFilter.set(fxpFilter.subarray(leftNotEmpty, rightNotEmpty + 1), packedFilterPtr);
-                                               packedFilterPtr += filterSize;
-                                       } else {
-                                               // fallback for old IE < 11, without subarray/set methods
-                                               for (idx = leftNotEmpty; idx <= rightNotEmpty; idx++) {
-                                                       packedFilter[packedFilterPtr++] = fxpFilter[idx];
-                                               }
-                                       }
-                               } else {
-                                       // zero data, write header only
-                                       packedFilter[packedFilterPtr++] = 0; // shift
-                                       
-                                       packedFilter[packedFilterPtr++] = 0; // size
-                               }
-                       }
-                       
-                       return packedFilter;
-               };
-               
-       },{"./resize_filter_info":7}],7:[function(require,module,exports){
-// Filter definitions to build tables for
-// resizing convolvers.
-//
-// Presets for quality 0..3. Filter functions + window size
-//
-               'use strict';
-               
-               module.exports = [{
-                       // Nearest neibor (Box)
-                       win: 0.5,
-                       filter: function filter(x) {
-                               return x >= -0.5 && x < 0.5 ? 1.0 : 0.0;
-                       }
-               }, {
-                       // Hamming
-                       win: 1.0,
-                       filter: function filter(x) {
-                               if (x <= -1.0 || x >= 1.0) {
-                                       return 0.0;
-                               }
-                               
-                               if (x > -1.19209290E-07 && x < 1.19209290E-07) {
-                                       return 1.0;
-                               }
-                               
-                               var xpi = x * Math.PI;
-                               return Math.sin(xpi) / xpi * (0.54 + 0.46 * Math.cos(xpi / 1.0));
-                       }
-               }, {
-                       // Lanczos, win = 2
-                       win: 2.0,
-                       filter: function filter(x) {
-                               if (x <= -2.0 || x >= 2.0) {
-                                       return 0.0;
-                               }
-                               
-                               if (x > -1.19209290E-07 && x < 1.19209290E-07) {
-                                       return 1.0;
-                               }
-                               
-                               var xpi = x * Math.PI;
-                               return Math.sin(xpi) / xpi * Math.sin(xpi / 2.0) / (xpi / 2.0);
-                       }
-               }, {
-                       // Lanczos, win = 3
-                       win: 3.0,
-                       filter: function filter(x) {
-                               if (x <= -3.0 || x >= 3.0) {
-                                       return 0.0;
-                               }
-                               
-                               if (x > -1.19209290E-07 && x < 1.19209290E-07) {
-                                       return 1.0;
-                               }
-                               
-                               var xpi = x * Math.PI;
-                               return Math.sin(xpi) / xpi * Math.sin(xpi / 3.0) / (xpi / 3.0);
-                       }
-               }];
-               
-       },{}],8:[function(require,module,exports){
-               'use strict';
-               
-               var createFilters = require('./resize_filter_gen');
-               
-               function resetAlpha(dst, width, height) {
-                       var ptr = 3,
-                               len = width * height * 4 | 0;
-                       
-                       while (ptr < len) {
-                               dst[ptr] = 0xFF;
-                               ptr = ptr + 4 | 0;
-                       }
-               }
-               
-               function asUint8Array(src) {
-                       return new Uint8Array(src.buffer, 0, src.byteLength);
-               }
-               
-               var IS_LE = true; // should not crash everything on module load in old browsers
-               
-               try {
-                       IS_LE = new Uint32Array(new Uint8Array([1, 0, 0, 0]).buffer)[0] === 1;
-               } catch (__) {}
-               
-               function copyInt16asLE(src, target, target_offset) {
-                       if (IS_LE) {
-                               target.set(asUint8Array(src), target_offset);
-                               return;
-                       }
-                       
-                       for (var ptr = target_offset, i = 0; i < src.length; i++) {
-                               var data = src[i];
-                               target[ptr++] = data & 0xFF;
-                               target[ptr++] = data >> 8 & 0xFF;
-                       }
-               }
-               
-               module.exports = function resize_wasm(options) {
-                       var src = options.src;
-                       var srcW = options.width;
-                       var srcH = options.height;
-                       var destW = options.toWidth;
-                       var destH = options.toHeight;
-                       var scaleX = options.scaleX || options.toWidth / options.width;
-                       var scaleY = options.scaleY || options.toHeight / options.height;
-                       var offsetX = options.offsetX || 0.0;
-                       var offsetY = options.offsetY || 0.0;
-                       var dest = options.dest || new Uint8Array(destW * destH * 4);
-                       var quality = typeof options.quality === 'undefined' ? 3 : options.quality;
-                       var alpha = options.alpha || false;
-                       var filtersX = createFilters(quality, srcW, destW, scaleX, offsetX),
-                               filtersY = createFilters(quality, srcH, destH, scaleY, offsetY); // destination is 0 too.
-                       
-                       var src_offset = 0; // buffer between convolve passes
-                       
-                       var tmp_offset = this.__align(src_offset + Math.max(src.byteLength, dest.byteLength));
-                       
-                       var filtersX_offset = this.__align(tmp_offset + srcH * destW * 4);
-                       
-                       var filtersY_offset = this.__align(filtersX_offset + filtersX.byteLength);
-                       
-                       var alloc_bytes = filtersY_offset + filtersY.byteLength;
-                       
-                       var instance = this.__instance('resize', alloc_bytes); //
-                       // Fill memory block with data to process
-                       //
-                       
-                       
-                       var mem = new Uint8Array(this.__memory.buffer);
-                       var mem32 = new Uint32Array(this.__memory.buffer); // 32-bit copy is much faster in chrome
-                       
-                       var src32 = new Uint32Array(src.buffer);
-                       mem32.set(src32); // We should guarantee LE bytes order. Filters are not big, so
-                       // speed difference is not significant vs direct .set()
-                       
-                       copyInt16asLE(filtersX, mem, filtersX_offset);
-                       copyInt16asLE(filtersY, mem, filtersY_offset); //
-                       // Now call webassembly method
-                       // emsdk does method names with '_'
-                       
-                       var fn = instance.exports.convolveHV || instance.exports._convolveHV;
-                       fn(filtersX_offset, filtersY_offset, tmp_offset, srcW, srcH, destW, destH); //
-                       // Copy data back to typed array
-                       //
-                       // 32-bit copy is much faster in chrome
-                       
-                       var dest32 = new Uint32Array(dest.buffer);
-                       dest32.set(new Uint32Array(this.__memory.buffer, 0, destH * destW)); // That's faster than doing checks in convolver.
-                       // !!! Note, canvas data is not premultipled. We don't need other
-                       // alpha corrections.
-                       
-                       if (!alpha) resetAlpha(dest, destW, destH);
-                       return dest;
-               };
-               
-       },{"./resize_filter_gen":6}],9:[function(require,module,exports){
-               'use strict';
-               
-               var GC_INTERVAL = 100;
-               
-               function Pool(create, idle) {
-                       this.create = create;
-                       this.available = [];
-                       this.acquired = {};
-                       this.lastId = 1;
-                       this.timeoutId = 0;
-                       this.idle = idle || 2000;
-               }
-               
-               Pool.prototype.acquire = function () {
-                       var _this = this;
-                       
-                       var resource;
-                       
-                       if (this.available.length !== 0) {
-                               resource = this.available.pop();
-                       } else {
-                               resource = this.create();
-                               resource.id = this.lastId++;
-                               
-                               resource.release = function () {
-                                       return _this.release(resource);
-                               };
-                       }
-                       
-                       this.acquired[resource.id] = resource;
-                       return resource;
-               };
-               
-               Pool.prototype.release = function (resource) {
-                       var _this2 = this;
-                       
-                       delete this.acquired[resource.id];
-                       resource.lastUsed = Date.now();
-                       this.available.push(resource);
-                       
-                       if (this.timeoutId === 0) {
-                               this.timeoutId = setTimeout(function () {
-                                       return _this2.gc();
-                               }, GC_INTERVAL);
-                       }
-               };
-               
-               Pool.prototype.gc = function () {
-                       var _this3 = this;
-                       
-                       var now = Date.now();
-                       this.available = this.available.filter(function (resource) {
-                               if (now - resource.lastUsed > _this3.idle) {
-                                       resource.destroy();
-                                       return false;
-                               }
-                               
-                               return true;
-                       });
-                       
-                       if (this.available.length !== 0) {
-                               this.timeoutId = setTimeout(function () {
-                                       return _this3.gc();
-                               }, GC_INTERVAL);
-                       } else {
-                               this.timeoutId = 0;
-                       }
-               };
-               
-               module.exports = Pool;
-               
-       },{}],10:[function(require,module,exports){
-// Add intermediate resizing steps when scaling down by a very large factor.
-//
-// For example, when resizing 10000x10000 down to 10x10, it'll resize it to
-// 300x300 first.
-//
-// It's needed because tiler has issues when the entire tile is scaled down
-// to a few pixels (1024px source tile with border size 3 should result in
-// at least 3+3+2 = 8px target tile, so max scale factor is 128 here).
-//
-// Also, adding intermediate steps can speed up processing if we use lower
-// quality algorithms for first stages.
-//
-               'use strict'; // min size = 0 results in infinite loop,
-// min size = 1 can consume large amount of memory
-               
-               var MIN_INNER_TILE_SIZE = 2;
-               
-               module.exports = function createStages(fromWidth, fromHeight, toWidth, toHeight, srcTileSize, destTileBorder) {
-                       var scaleX = toWidth / fromWidth;
-                       var scaleY = toHeight / fromHeight; // derived from createRegions equation:
-                       // innerTileWidth = pixelFloor(srcTileSize * scaleX) - 2 * destTileBorder;
-                       
-                       var minScale = (2 * destTileBorder + MIN_INNER_TILE_SIZE + 1) / srcTileSize; // refuse to scale image multiple times by less than twice each time,
-                       // it could only happen because of invalid options
-                       
-                       if (minScale > 0.5) return [[toWidth, toHeight]];
-                       var stageCount = Math.ceil(Math.log(Math.min(scaleX, scaleY)) / Math.log(minScale)); // no additional resizes are necessary,
-                       // stageCount can be zero or be negative when enlarging the image
-                       
-                       if (stageCount <= 1) return [[toWidth, toHeight]];
-                       var result = [];
-                       
-                       for (var i = 0; i < stageCount; i++) {
-                               var width = Math.round(Math.pow(Math.pow(fromWidth, stageCount - i - 1) * Math.pow(toWidth, i + 1), 1 / stageCount));
-                               var height = Math.round(Math.pow(Math.pow(fromHeight, stageCount - i - 1) * Math.pow(toHeight, i + 1), 1 / stageCount));
-                               result.push([width, height]);
-                       }
-                       
-                       return result;
-               };
-               
-       },{}],11:[function(require,module,exports){
-// Split original image into multiple 1024x1024 chunks to reduce memory usage
-// (images have to be unpacked into typed arrays for resizing) and allow
-// parallel processing of multiple tiles at a time.
-//
-               'use strict';
-               /*
-                * pixelFloor and pixelCeil are modified versions of Math.floor and Math.ceil
-                * functions which take into account floating point arithmetic errors.
-                * Those errors can cause undesired increments/decrements of sizes and offsets:
-                * Math.ceil(36 / (36 / 500)) = 501
-                * pixelCeil(36 / (36 / 500)) = 500
-                */
-               
-               var PIXEL_EPSILON = 1e-5;
-               
-               function pixelFloor(x) {
-                       var nearest = Math.round(x);
-                       
-                       if (Math.abs(x - nearest) < PIXEL_EPSILON) {
-                               return nearest;
-                       }
-                       
-                       return Math.floor(x);
-               }
-               
-               function pixelCeil(x) {
-                       var nearest = Math.round(x);
-                       
-                       if (Math.abs(x - nearest) < PIXEL_EPSILON) {
-                               return nearest;
-                       }
-                       
-                       return Math.ceil(x);
-               }
-               
-               module.exports = function createRegions(options) {
-                       var scaleX = options.toWidth / options.width;
-                       var scaleY = options.toHeight / options.height;
-                       var innerTileWidth = pixelFloor(options.srcTileSize * scaleX) - 2 * options.destTileBorder;
-                       var innerTileHeight = pixelFloor(options.srcTileSize * scaleY) - 2 * options.destTileBorder; // prevent infinite loop, this should never happen
-                       
-                       if (innerTileWidth < 1 || innerTileHeight < 1) {
-                               throw new Error('Internal error in pica: target tile width/height is too small.');
-                       }
-                       
-                       var x, y;
-                       var innerX, innerY, toTileWidth, toTileHeight;
-                       var tiles = [];
-                       var tile; // we go top-to-down instead of left-to-right to make image displayed from top to
-                       // doesn in the browser
-                       
-                       for (innerY = 0; innerY < options.toHeight; innerY += innerTileHeight) {
-                               for (innerX = 0; innerX < options.toWidth; innerX += innerTileWidth) {
-                                       x = innerX - options.destTileBorder;
-                                       
-                                       if (x < 0) {
-                                               x = 0;
-                                       }
-                                       
-                                       toTileWidth = innerX + innerTileWidth + options.destTileBorder - x;
-                                       
-                                       if (x + toTileWidth >= options.toWidth) {
-                                               toTileWidth = options.toWidth - x;
-                                       }
-                                       
-                                       y = innerY - options.destTileBorder;
-                                       
-                                       if (y < 0) {
-                                               y = 0;
-                                       }
-                                       
-                                       toTileHeight = innerY + innerTileHeight + options.destTileBorder - y;
-                                       
-                                       if (y + toTileHeight >= options.toHeight) {
-                                               toTileHeight = options.toHeight - y;
-                                       }
-                                       
-                                       tile = {
-                                               toX: x,
-                                               toY: y,
-                                               toWidth: toTileWidth,
-                                               toHeight: toTileHeight,
-                                               toInnerX: innerX,
-                                               toInnerY: innerY,
-                                               toInnerWidth: innerTileWidth,
-                                               toInnerHeight: innerTileHeight,
-                                               offsetX: x / scaleX - pixelFloor(x / scaleX),
-                                               offsetY: y / scaleY - pixelFloor(y / scaleY),
-                                               scaleX: scaleX,
-                                               scaleY: scaleY,
-                                               x: pixelFloor(x / scaleX),
-                                               y: pixelFloor(y / scaleY),
-                                               width: pixelCeil(toTileWidth / scaleX),
-                                               height: pixelCeil(toTileHeight / scaleY)
-                                       };
-                                       tiles.push(tile);
-                               }
-                       }
-                       
-                       return tiles;
-               };
-               
-       },{}],12:[function(require,module,exports){
-               'use strict';
-               
-               function objClass(obj) {
-                       return Object.prototype.toString.call(obj);
-               }
-               
-               module.exports.isCanvas = function isCanvas(element) {
-                       //return (element.nodeName && element.nodeName.toLowerCase() === 'canvas') ||
-                       var cname = objClass(element);
-                       return cname === '[object HTMLCanvasElement]'
-                               /* browser */
-                               || cname === '[object Canvas]'
-                               /* node-canvas */
-                               ;
-               };
-               
-               module.exports.isImage = function isImage(element) {
-                       //return element.nodeName && element.nodeName.toLowerCase() === 'img';
-                       return objClass(element) === '[object HTMLImageElement]';
-               };
-               
-               module.exports.limiter = function limiter(concurrency) {
-                       var active = 0,
-                               queue = [];
-                       
-                       function roll() {
-                               if (active < concurrency && queue.length) {
-                                       active++;
-                                       queue.shift()();
-                               }
-                       }
-                       
-                       return function limit(fn) {
-                               return new Promise(function (resolve, reject) {
-                                       queue.push(function () {
-                                               fn().then(function (result) {
-                                                       resolve(result);
-                                                       active--;
-                                                       roll();
-                                               }, function (err) {
-                                                       reject(err);
-                                                       active--;
-                                                       roll();
-                                               });
-                                       });
-                                       roll();
-                               });
-                       };
-               };
-               
-               module.exports.cib_quality_name = function cib_quality_name(num) {
-                       switch (num) {
-                               case 0:
-                                       return 'pixelated';
-                               
-                               case 1:
-                                       return 'low';
-                               
-                               case 2:
-                                       return 'medium';
-                       }
-                       
-                       return 'high';
-               };
-               
-               module.exports.cib_support = function cib_support() {
-                       return Promise.resolve().then(function () {
-                               if (typeof createImageBitmap === 'undefined' || typeof document === 'undefined') {
-                                       return false;
-                               }
-                               
-                               var c = document.createElement('canvas');
-                               c.width = 100;
-                               c.height = 100;
-                               return createImageBitmap(c, 0, 0, 100, 100, {
-                                       resizeWidth: 10,
-                                       resizeHeight: 10,
-                                       resizeQuality: 'high'
-                               }).then(function (bitmap) {
-                                       var status = bitmap.width === 10; // Branch below is filtered on upper level. We do not call resize
-                                       // detection for basic ImageBitmap.
-                                       //
-                                       // https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap
-                                       // old Crome 51 has ImageBitmap without .close(). Then this code
-                                       // will throw and return 'false' as expected.
-                                       //
-                                       
-                                       bitmap.close();
-                                       c = null;
-                                       return status;
-                               });
-                       }).catch(function () {
-                               return false;
-                       });
-               };
-               
-       },{}],13:[function(require,module,exports){
-// Web Worker wrapper for image resize function
-               'use strict';
-               
-               module.exports = function () {
-                       var MathLib = require('./mathlib');
-                       
-                       var mathLib;
-                       /* eslint-disable no-undef */
-                       
-                       onmessage = function onmessage(ev) {
-                               var opts = ev.data.opts;
-                               if (!mathLib) mathLib = new MathLib(ev.data.features); // Use multimath's sync auto-init. Avoid Promise use in old browsers,
-                               // because polyfills are not propagated to webworker.
-                               
-                               var result = mathLib.resizeAndUnsharp(opts);
-                               postMessage({
-                                       result: result
-                               }, [result.buffer]);
-                       };
-               };
-               
-       },{"./mathlib":1}],14:[function(require,module,exports){
-// Calculate Gaussian blur of an image using IIR filter
-// The method is taken from Intel's white paper and code example attached to it:
-// https://software.intel.com/en-us/articles/iir-gaussian-blur-filter
-// -implementation-using-intel-advanced-vector-extensions
-               
-               var a0, a1, a2, a3, b1, b2, left_corner, right_corner;
-               
-               function gaussCoef(sigma) {
-                       if (sigma < 0.5) {
-                               sigma = 0.5;
-                       }
-                       
-                       var a = Math.exp(0.726 * 0.726) / sigma,
-                               g1 = Math.exp(-a),
-                               g2 = Math.exp(-2 * a),
-                               k = (1 - g1) * (1 - g1) / (1 + 2 * a * g1 - g2);
-                       
-                       a0 = k;
-                       a1 = k * (a - 1) * g1;
-                       a2 = k * (a + 1) * g1;
-                       a3 = -k * g2;
-                       b1 = 2 * g1;
-                       b2 = -g2;
-                       left_corner = (a0 + a1) / (1 - b1 - b2);
-                       right_corner = (a2 + a3) / (1 - b1 - b2);
-                       
-                       // Attempt to force type to FP32.
-                       return new Float32Array([ a0, a1, a2, a3, b1, b2, left_corner, right_corner ]);
-               }
-               
-               function convolveMono16(src, out, line, coeff, width, height) {
-                       // takes src image and writes the blurred and transposed result into out
-                       
-                       var prev_src, curr_src, curr_out, prev_out, prev_prev_out;
-                       var src_index, out_index, line_index;
-                       var i, j;
-                       var coeff_a0, coeff_a1, coeff_b1, coeff_b2;
-                       
-                       for (i = 0; i < height; i++) {
-                               src_index = i * width;
-                               out_index = i;
-                               line_index = 0;
-                               
-                               // left to right
-                               prev_src = src[src_index];
-                               prev_prev_out = prev_src * coeff[6];
-                               prev_out = prev_prev_out;
-                               
-                               coeff_a0 = coeff[0];
-                               coeff_a1 = coeff[1];
-                               coeff_b1 = coeff[4];
-                               coeff_b2 = coeff[5];
-                               
-                               for (j = 0; j < width; j++) {
-                                       curr_src = src[src_index];
-                                       
-                                       curr_out = curr_src * coeff_a0 +
-                                               prev_src * coeff_a1 +
-                                               prev_out * coeff_b1 +
-                                               prev_prev_out * coeff_b2;
-                                       
-                                       prev_prev_out = prev_out;
-                                       prev_out = curr_out;
-                                       prev_src = curr_src;
-                                       
-                                       line[line_index] = prev_out;
-                                       line_index++;
-                                       src_index++;
-                               }
-                               
-                               src_index--;
-                               line_index--;
-                               out_index += height * (width - 1);
-                               
-                               // right to left
-                               prev_src = src[src_index];
-                               prev_prev_out = prev_src * coeff[7];
-                               prev_out = prev_prev_out;
-                               curr_src = prev_src;
-                               
-                               coeff_a0 = coeff[2];
-                               coeff_a1 = coeff[3];
-                               
-                               for (j = width - 1; j >= 0; j--) {
-                                       curr_out = curr_src * coeff_a0 +
-                                               prev_src * coeff_a1 +
-                                               prev_out * coeff_b1 +
-                                               prev_prev_out * coeff_b2;
-                                       
-                                       prev_prev_out = prev_out;
-                                       prev_out = curr_out;
-                                       
-                                       prev_src = curr_src;
-                                       curr_src = src[src_index];
-                                       
-                                       out[out_index] = line[line_index] + prev_out;
-                                       
-                                       src_index--;
-                                       line_index--;
-                                       out_index -= height;
-                               }
-                       }
-               }
-               
-               
-               function blurMono16(src, width, height, radius) {
-                       // Quick exit on zero radius
-                       if (!radius) { return; }
-                       
-                       var out      = new Uint16Array(src.length),
-                               tmp_line = new Float32Array(Math.max(width, height));
-                       
-                       var coeff = gaussCoef(radius);
-                       
-                       convolveMono16(src, out, tmp_line, coeff, width, height, radius);
-                       convolveMono16(out, src, tmp_line, coeff, height, width, radius);
-               }
-               
-               module.exports = blurMono16;
-               
-       },{}],15:[function(require,module,exports){
-               if (typeof Object.create === 'function') {
-                       // implementation from standard node.js 'util' module
-                       module.exports = function inherits(ctor, superCtor) {
-                               ctor.super_ = superCtor
-                               ctor.prototype = Object.create(superCtor.prototype, {
-                                       constructor: {
-                                               value: ctor,
-                                               enumerable: false,
-                                               writable: true,
-                                               configurable: true
-                                       }
-                               });
-                       };
-               } else {
-                       // old school shim for old browsers
-                       module.exports = function inherits(ctor, superCtor) {
-                               ctor.super_ = superCtor
-                               var TempCtor = function () {}
-                               TempCtor.prototype = superCtor.prototype
-                               ctor.prototype = new TempCtor()
-                               ctor.prototype.constructor = ctor
-                       }
-               }
-               
-       },{}],16:[function(require,module,exports){
-               'use strict';
-               
-               
-               var assign         = require('object-assign');
-               var base64decode   = require('./lib/base64decode');
-               var hasWebAssembly = require('./lib/wa_detect');
-               
-               
-               var DEFAULT_OPTIONS = {
-                       js: true,
-                       wasm: true
-               };
-               
-               
-               function MultiMath(options) {
-                       if (!(this instanceof MultiMath)) return new MultiMath(options);
-                       
-                       var opts = assign({}, DEFAULT_OPTIONS, options || {});
-                       
-                       this.options         = opts;
-                       
-                       this.__cache         = {};
-                       this.has_wasm        = hasWebAssembly();
-                       
-                       this.__init_promise  = null;
-                       this.__modules       = opts.modules || {};
-                       this.__memory        = null;
-                       this.__wasm          = {};
-                       
-                       this.__isLE = ((new Uint32Array((new Uint8Array([ 1, 0, 0, 0 ])).buffer))[0] === 1);
-                       
-                       if (!this.options.js && !this.options.wasm) {
-                               throw new Error('mathlib: at least "js" or "wasm" should be enabled');
-                       }
-               }
-               
-               
-               MultiMath.prototype.use = function (module) {
-                       this.__modules[module.name] = module;
-                       
-                       // Pin the best possible implementation
-                       if (!this.has_wasm || !this.options.wasm || !module.wasm_fn) {
-                               this[module.name] = module.fn;
-                       } else {
-                               this[module.name] = module.wasm_fn;
-                       }
-                       
-                       return this;
-               };
-               
-               
-               MultiMath.prototype.init = function () {
-                       if (this.__init_promise) return this.__init_promise;
-                       
-                       if (!this.options.js && this.options.wasm && !this.has_wasm) {
-                               return Promise.reject(new Error('mathlib: only "wasm" was enabled, but it\'s not supported'));
-                       }
-                       
-                       var self = this;
-                       
-                       this.__init_promise = Promise.all(Object.keys(self.__modules).map(function (name) {
-                                       var module = self.__modules[name];
-                                       
-                                       if (!self.has_wasm || !self.options.wasm || !module.wasm_fn) return null;
-                                       
-                                       // If already compiled - exit
-                                       if (self.__wasm[name]) return null;
-                                       
-                                       // Compile wasm source
-                                       return WebAssembly.compile(self.__base64decode(module.wasm_src))
-                                               .then(function (m) { self.__wasm[name] = m; });
-                               }))
-                               .then(function () { return self; });
-                       
-                       return this.__init_promise;
-               };
-
-
-////////////////////////////////////////////////////////////////////////////////
-// Methods below are for internal use from plugins
-
-
-// Simple decode base64 to typed array. Useful to load embedded webassembly
-// code. You probably don't need to call this method directly.
-//
-               MultiMath.prototype.__base64decode = base64decode;
-
-
-// Increase current memory to include specified number of bytes. Do nothing if
-// size is already ok. You probably don't need to call this method directly,
-// because it will be invoked from `.__instance()`.
-//
-               MultiMath.prototype.__reallocate = function mem_grow_to(bytes) {
-                       if (!this.__memory) {
-                               this.__memory = new WebAssembly.Memory({
-                                       initial: Math.ceil(bytes / (64 * 1024))
-                               });
-                               return this.__memory;
-                       }
-                       
-                       var mem_size = this.__memory.buffer.byteLength;
-                       
-                       if (mem_size < bytes) {
-                               this.__memory.grow(Math.ceil((bytes - mem_size) / (64 * 1024)));
-                       }
-                       
-                       return this.__memory;
-               };
-
-
-// Returns instantinated webassembly item by name, with specified memory size
-// and environment.
-// - use cache if available
-// - do sync module init, if async init was not called earlier
-// - allocate memory if not enougth
-// - can export functions to webassembly via "env_extra",
-//   for example, { exp: Math.exp }
-//
-               MultiMath.prototype.__instance = function instance(name, memsize, env_extra) {
-                       if (memsize) this.__reallocate(memsize);
-                       
-                       // If .init() was not called, do sync compile
-                       if (!this.__wasm[name]) {
-                               var module = this.__modules[name];
-                               this.__wasm[name] = new WebAssembly.Module(this.__base64decode(module.wasm_src));
-                       }
-                       
-                       if (!this.__cache[name]) {
-                               var env_base = {
-                                       memoryBase: 0,
-                                       memory: this.__memory,
-                                       tableBase: 0,
-                                       table: new WebAssembly.Table({ initial: 0, element: 'anyfunc' })
-                               };
-                               
-                               this.__cache[name] = new WebAssembly.Instance(this.__wasm[name], {
-                                       env: assign(env_base, env_extra || {})
-                               });
-                       }
-                       
-                       return this.__cache[name];
-               };
-
-
-// Helper to calculate memory aligh for pointers. Webassembly does not require
-// this, but you may wish to experiment. Default base = 8;
-//
-               MultiMath.prototype.__align = function align(number, base) {
-                       base = base || 8;
-                       var reminder = number % base;
-                       return number + (reminder ? base - reminder : 0);
-               };
-               
-               
-               module.exports = MultiMath;
-               
-       },{"./lib/base64decode":17,"./lib/wa_detect":23,"object-assign":24}],17:[function(require,module,exports){
-// base64 decode str -> Uint8Array, to load WA modules
-//
-               'use strict';
-               
-               
-               var BASE64_MAP = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
-               
-               
-               module.exports = function base64decode(str) {
-                       var input = str.replace(/[\r\n=]/g, ''), // remove CR/LF & padding to simplify scan
-                               max   = input.length;
-                       
-                       var out = new Uint8Array((max * 3) >> 2);
-                       
-                       // Collect by 6*4 bits (3 bytes)
-                       
-                       var bits = 0;
-                       var ptr  = 0;
-                       
-                       for (var idx = 0; idx < max; idx++) {
-                               if ((idx % 4 === 0) && idx) {
-                                       out[ptr++] = (bits >> 16) & 0xFF;
-                                       out[ptr++] = (bits >> 8) & 0xFF;
-                                       out[ptr++] = bits & 0xFF;
-                               }
-                               
-                               bits = (bits << 6) | BASE64_MAP.indexOf(input.charAt(idx));
-                       }
-                       
-                       // Dump tail
-                       
-                       var tailbits = (max % 4) * 6;
-                       
-                       if (tailbits === 0) {
-                               out[ptr++] = (bits >> 16) & 0xFF;
-                               out[ptr++] = (bits >> 8) & 0xFF;
-                               out[ptr++] = bits & 0xFF;
-                       } else if (tailbits === 18) {
-                               out[ptr++] = (bits >> 10) & 0xFF;
-                               out[ptr++] = (bits >> 2) & 0xFF;
-                       } else if (tailbits === 12) {
-                               out[ptr++] = (bits >> 4) & 0xFF;
-                       }
-                       
-                       return out;
-               };
-               
-       },{}],18:[function(require,module,exports){
-// Calculates 16-bit precision HSL lightness from 8-bit rgba buffer
-//
-               'use strict';
-               
-               
-               module.exports = function hsl_l16_js(img, width, height) {
-                       var size = width * height;
-                       var out = new Uint16Array(size);
-                       var r, g, b, min, max;
-                       for (var i = 0; i < size; i++) {
-                               r = img[4 * i];
-                               g = img[4 * i + 1];
-                               b = img[4 * i + 2];
-                               max = (r >= g && r >= b) ? r : (g >= b && g >= r) ? g : b;
-                               min = (r <= g && r <= b) ? r : (g <= b && g <= r) ? g : b;
-                               out[i] = (max + min) * 257 >> 1;
-                       }
-                       return out;
-               };
-               
-       },{}],19:[function(require,module,exports){
-               'use strict';
-               
-               module.exports = {
-                       name:     'unsharp_mask',
-                       fn:       require('./unsharp_mask'),
-                       wasm_fn:  require('./unsharp_mask_wasm'),
-                       wasm_src: require('./unsharp_mask_wasm_base64')
-               };
-               
-       },{"./unsharp_mask":20,"./unsharp_mask_wasm":21,"./unsharp_mask_wasm_base64":22}],20:[function(require,module,exports){
-// Unsharp mask filter
-//
-// http://stackoverflow.com/a/23322820/1031804
-// USM(O) = O + (2 * (Amount / 100) * (O - GB))
-// GB - gaussian blur.
-//
-// Image is converted from RGB to HSL, unsharp mask is applied to the
-// lightness channel and then image is converted back to RGB.
-//
-               'use strict';
-               
-               
-               var glur_mono16 = require('glur/mono16');
-               var hsl_l16     = require('./hsl_l16');
-               
-               
-               module.exports = function unsharp(img, width, height, amount, radius, threshold) {
-                       var r, g, b;
-                       var h, s, l;
-                       var min, max;
-                       var m1, m2, hShifted;
-                       var diff, iTimes4;
-                       
-                       if (amount === 0 || radius < 0.5) {
-                               return;
-                       }
-                       if (radius > 2.0) {
-                               radius = 2.0;
-                       }
-                       
-                       var lightness = hsl_l16(img, width, height);
-                       
-                       var blured = new Uint16Array(lightness); // copy, because blur modify src
-                       
-                       glur_mono16(blured, width, height, radius);
-                       
-                       var amountFp = (amount / 100 * 0x1000 + 0.5)|0;
-                       var thresholdFp = (threshold * 257)|0;
-                       
-                       var size = width * height;
-                       
-                       /* eslint-disable indent */
-                       for (var i = 0; i < size; i++) {
-                               diff = 2 * (lightness[i] - blured[i]);
-                               
-                               if (Math.abs(diff) >= thresholdFp) {
-                                       iTimes4 = i * 4;
-                                       r = img[iTimes4];
-                                       g = img[iTimes4 + 1];
-                                       b = img[iTimes4 + 2];
-                                       
-                                       // convert RGB to HSL
-                                       // take RGB, 8-bit unsigned integer per each channel
-                                       // save HSL, H and L are 16-bit unsigned integers, S is 12-bit unsigned integer
-                                       // math is taken from here: http://www.easyrgb.com/index.php?X=MATH&H=18
-                                       // and adopted to be integer (fixed point in fact) for sake of performance
-                                       max = (r >= g && r >= b) ? r : (g >= r && g >= b) ? g : b; // min and max are in [0..0xff]
-                                       min = (r <= g && r <= b) ? r : (g <= r && g <= b) ? g : b;
-                                       l = (max + min) * 257 >> 1; // l is in [0..0xffff] that is caused by multiplication by 257
-                                       
-                                       if (min === max) {
-                                               h = s = 0;
-                                       } else {
-                                               s = (l <= 0x7fff) ?
-                                                       (((max - min) * 0xfff) / (max + min))|0 :
-                                                       (((max - min) * 0xfff) / (2 * 0xff - max - min))|0; // s is in [0..0xfff]
-                                               // h could be less 0, it will be fixed in backward conversion to RGB, |h| <= 0xffff / 6
-                                               h = (r === max) ? (((g - b) * 0xffff) / (6 * (max - min)))|0
-                                                       : (g === max) ? 0x5555 + ((((b - r) * 0xffff) / (6 * (max - min)))|0) // 0x5555 == 0xffff / 3
-                                                               : 0xaaaa + ((((r - g) * 0xffff) / (6 * (max - min)))|0); // 0xaaaa == 0xffff * 2 / 3
-                                       }
-                                       
-                                       // add unsharp mask mask to the lightness channel
-                                       l += (amountFp * diff + 0x800) >> 12;
-                                       if (l > 0xffff) {
-                                               l = 0xffff;
-                                       } else if (l < 0) {
-                                               l = 0;
-                                       }
-                                       
-                                       // convert HSL back to RGB
-                                       // for information about math look above
-                                       if (s === 0) {
-                                               r = g = b = l >> 8;
-                                       } else {
-                                               m2 = (l <= 0x7fff) ? (l * (0x1000 + s) + 0x800) >> 12 :
-                                                       l  + (((0xffff - l) * s + 0x800) >>  12);
-                                               m1 = 2 * l - m2 >> 8;
-                                               m2 >>= 8;
-                                               // save result to RGB channels
-                                               // R channel
-                                               hShifted = (h + 0x5555) & 0xffff; // 0x5555 == 0xffff / 3
-                                               r = (hShifted >= 0xaaaa) ? m1 // 0xaaaa == 0xffff * 2 / 3
-                                                       : (hShifted >= 0x7fff) ?  m1 + ((m2 - m1) * 6 * (0xaaaa - hShifted) + 0x8000 >> 16)
-                                                               : (hShifted >= 0x2aaa) ? m2 // 0x2aaa == 0xffff / 6
-                                                                       : m1 + ((m2 - m1) * 6 * hShifted + 0x8000 >> 16);
-                                               // G channel
-                                               hShifted = h & 0xffff;
-                                               g = (hShifted >= 0xaaaa) ? m1 // 0xaaaa == 0xffff * 2 / 3
-                                                       : (hShifted >= 0x7fff) ?  m1 + ((m2 - m1) * 6 * (0xaaaa - hShifted) + 0x8000 >> 16)
-                                                               : (hShifted >= 0x2aaa) ? m2 // 0x2aaa == 0xffff / 6
-                                                                       : m1 + ((m2 - m1) * 6 * hShifted + 0x8000 >> 16);
-                                               // B channel
-                                               hShifted = (h - 0x5555) & 0xffff;
-                                               b = (hShifted >= 0xaaaa) ? m1 // 0xaaaa == 0xffff * 2 / 3
-                                                       : (hShifted >= 0x7fff) ?  m1 + ((m2 - m1) * 6 * (0xaaaa - hShifted) + 0x8000 >> 16)
-                                                               : (hShifted >= 0x2aaa) ? m2 // 0x2aaa == 0xffff / 6
-                                                                       : m1 + ((m2 - m1) * 6 * hShifted + 0x8000 >> 16);
-                                       }
-                                       
-                                       img[iTimes4] = r;
-                                       img[iTimes4 + 1] = g;
-                                       img[iTimes4 + 2] = b;
-                               }
-                       }
-               };
-               
-       },{"./hsl_l16":18,"glur/mono16":14}],21:[function(require,module,exports){
-               'use strict';
-               
-               
-               module.exports = function unsharp(img, width, height, amount, radius, threshold) {
-                       if (amount === 0 || radius < 0.5) {
-                               return;
-                       }
-                       
-                       if (radius > 2.0) {
-                               radius = 2.0;
-                       }
-                       
-                       var pixels = width * height;
-                       
-                       var img_bytes_cnt        = pixels * 4;
-                       var hsl_bytes_cnt        = pixels * 2;
-                       var blur_bytes_cnt       = pixels * 2;
-                       var blur_line_byte_cnt   = Math.max(width, height) * 4; // float32 array
-                       var blur_coeffs_byte_cnt = 8 * 4; // float32 array
-                       
-                       var img_offset         = 0;
-                       var hsl_offset         = img_bytes_cnt;
-                       var blur_offset        = hsl_offset + hsl_bytes_cnt;
-                       var blur_tmp_offset    = blur_offset + blur_bytes_cnt;
-                       var blur_line_offset   = blur_tmp_offset + blur_bytes_cnt;
-                       var blur_coeffs_offset = blur_line_offset + blur_line_byte_cnt;
-                       
-                       var instance = this.__instance(
-                               'unsharp_mask',
-                               img_bytes_cnt + hsl_bytes_cnt + blur_bytes_cnt * 2 + blur_line_byte_cnt + blur_coeffs_byte_cnt,
-                               { exp: Math.exp }
-                       );
-                       
-                       // 32-bit copy is much faster in chrome
-                       var img32 = new Uint32Array(img.buffer);
-                       var mem32 = new Uint32Array(this.__memory.buffer);
-                       mem32.set(img32);
-                       
-                       // HSL
-                       var fn = instance.exports.hsl_l16 || instance.exports._hsl_l16;
-                       fn(img_offset, hsl_offset, width, height);
-                       
-                       // BLUR
-                       fn = instance.exports.blurMono16 || instance.exports._blurMono16;
-                       fn(hsl_offset, blur_offset, blur_tmp_offset,
-                               blur_line_offset, blur_coeffs_offset, width, height, radius);
-                       
-                       // UNSHARP
-                       fn = instance.exports.unsharp || instance.exports._unsharp;
-                       fn(img_offset, img_offset, hsl_offset,
-                               blur_offset, width, height, amount, threshold);
-                       
-                       // 32-bit copy is much faster in chrome
-                       img32.set(new Uint32Array(this.__memory.buffer, 0, pixels));
-               };
-               
-       },{}],22:[function(require,module,exports){
-// This is autogenerated file from math.wasm, don't edit.
-//
-               'use strict';
-               
-               /* eslint-disable max-len */
-               module.exports = 'AGFzbQEAAAABMQZgAXwBfGACfX8AYAZ/f39/f38AYAh/f39/f39/fQBgBH9/f38AYAh/f39/f39/fwACGQIDZW52A2V4cAAAA2VudgZtZW1vcnkCAAEDBgUBAgMEBQQEAXAAAAdMBRZfX2J1aWxkX2dhdXNzaWFuX2NvZWZzAAEOX19nYXVzczE2X2xpbmUAAgpibHVyTW9ubzE2AAMHaHNsX2wxNgAEB3Vuc2hhcnAABQkBAAqJEAXZAQEGfAJAIAFE24a6Q4Ia+z8gALujIgOaEAAiBCAEoCIGtjgCECABIANEAAAAAAAAAMCiEAAiBbaMOAIUIAFEAAAAAAAA8D8gBKEiAiACoiAEIAMgA6CiRAAAAAAAAPA/oCAFoaMiArY4AgAgASAEIANEAAAAAAAA8L+gIAKioiIHtjgCBCABIAQgA0QAAAAAAADwP6AgAqKiIgO2OAIIIAEgBSACoiIEtow4AgwgASACIAegIAVEAAAAAAAA8D8gBqGgIgKjtjgCGCABIAMgBKEgAqO2OAIcCwu3AwMDfwR9CHwCQCADKgIUIQkgAyoCECEKIAMqAgwhCyADKgIIIQwCQCAEQX9qIgdBAEgiCA0AIAIgAC8BALgiDSADKgIYu6IiDiAJuyIQoiAOIAq7IhGiIA0gAyoCBLsiEqIgAyoCALsiEyANoqCgoCIPtjgCACACQQRqIQIgAEECaiEAIAdFDQAgBCEGA0AgAiAOIBCiIA8iDiARoiANIBKiIBMgAC8BALgiDaKgoKAiD7Y4AgAgAkEEaiECIABBAmohACAGQX9qIgZBAUoNAAsLAkAgCA0AIAEgByAFbEEBdGogAEF+ai8BACIIuCINIAu7IhGiIA0gDLsiEqKgIA0gAyoCHLuiIg4gCrsiE6KgIA4gCbsiFKKgIg8gAkF8aioCALugqzsBACAHRQ0AIAJBeGohAiAAQXxqIQBBACAFQQF0ayEHIAEgBSAEQQF0QXxqbGohBgNAIAghAyAALwEAIQggBiANIBGiIAO4Ig0gEqKgIA8iECAToqAgDiAUoqAiDyACKgIAu6CrOwEAIAYgB2ohBiAAQX5qIQAgAkF8aiECIBAhDiAEQX9qIgRBAUoNAAsLCwvfAgIDfwZ8AkAgB0MAAAAAWw0AIARE24a6Q4Ia+z8gB0MAAAA/l7ujIgyaEAAiDSANoCIPtjgCECAEIAxEAAAAAAAAAMCiEAAiDraMOAIUIAREAAAAAAAA8D8gDaEiCyALoiANIAwgDKCiRAAAAAAAAPA/oCAOoaMiC7Y4AgAgBCANIAxEAAAAAAAA8L+gIAuioiIQtjgCBCAEIA0gDEQAAAAAAADwP6AgC6KiIgy2OAIIIAQgDiALoiINtow4AgwgBCALIBCgIA5EAAAAAAAA8D8gD6GgIgujtjgCGCAEIAwgDaEgC6O2OAIcIAYEQCAFQQF0IQogBiEJIAIhCANAIAAgCCADIAQgBSAGEAIgACAKaiEAIAhBAmohCCAJQX9qIgkNAAsLIAVFDQAgBkEBdCEIIAUhAANAIAIgASADIAQgBiAFEAIgAiAIaiECIAFBAmohASAAQX9qIgANAAsLC7wBAQV/IAMgAmwiAwRAQQAgA2shBgNAIAAoAgAiBEEIdiIHQf8BcSECAn8gBEH/AXEiAyAEQRB2IgRB/wFxIgVPBEAgAyIIIAMgAk8NARoLIAQgBCAHIAIgA0kbIAIgBUkbQf8BcQshCAJAIAMgAk0EQCADIAVNDQELIAQgByAEIAMgAk8bIAIgBUsbQf8BcSEDCyAAQQRqIQAgASADIAhqQYECbEEBdjsBACABQQJqIQEgBkEBaiIGDQALCwvTBgEKfwJAIAazQwAAgEWUQwAAyEKVu0QAAAAAAADgP6CqIQ0gBSAEbCILBEAgB0GBAmwhDgNAQQAgAi8BACADLwEAayIGQQF0IgdrIAcgBkEASBsgDk8EQCAAQQJqLQAAIQUCfyAALQAAIgYgAEEBai0AACIESSIJRQRAIAYiCCAGIAVPDQEaCyAFIAUgBCAEIAVJGyAGIARLGwshCAJ/IAYgBE0EQCAGIgogBiAFTQ0BGgsgBSAFIAQgBCAFSxsgCRsLIgogCGoiD0GBAmwiEEEBdiERQQAhDAJ/QQAiCSAIIApGDQAaIAggCmsiCUH/H2wgD0H+AyAIayAKayAQQYCABEkbbSEMIAYgCEYEQCAEIAVrQf//A2wgCUEGbG0MAQsgBSAGayAGIARrIAQgCEYiBhtB//8DbCAJQQZsbUHVqgFBqtUCIAYbagshCSARIAcgDWxBgBBqQQx1aiIGQQAgBkEAShsiBkH//wMgBkH//wNIGyEGAkACfwJAIAxB//8DcSIFBEAgBkH//wFKDQEgBUGAIGogBmxBgBBqQQx2DAILIAZBCHYiBiEFIAYhBAwCCyAFIAZB//8Dc2xBgBBqQQx2IAZqCyIFQQh2IQcgBkEBdCAFa0EIdiIGIQQCQCAJQdWqAWpB//8DcSIFQanVAksNACAFQf//AU8EQEGq1QIgBWsgByAGa2xBBmxBgIACakEQdiAGaiEEDAELIAchBCAFQanVAEsNACAFIAcgBmtsQQZsQYCAAmpBEHYgBmohBAsCfyAGIgUgCUH//wNxIghBqdUCSw0AGkGq1QIgCGsgByAGa2xBBmxBgIACakEQdiAGaiAIQf//AU8NABogByIFIAhBqdUASw0AGiAIIAcgBmtsQQZsQYCAAmpBEHYgBmoLIQUgCUGr1QJqQf//A3EiCEGp1QJLDQAgCEH//wFPBEBBqtUCIAhrIAcgBmtsQQZsQYCAAmpBEHYgBmohBgwBCyAIQanVAEsEQCAHIQYMAQsgCCAHIAZrbEEGbEGAgAJqQRB2IAZqIQYLIAEgBDoAACABQQFqIAU6AAAgAUECaiAGOgAACyADQQJqIQMgAkECaiECIABBBGohACABQQRqIQEgC0F/aiILDQALCwsL';
-               
-       },{}],23:[function(require,module,exports){
-// Detect WebAssembly support.
-// - Check global WebAssembly object
-// - Try to load simple module (can be disabled via CSP)
-//
-               'use strict';
-               
-               
-               var wa;
-               
-               
-               module.exports = function hasWebAssembly() {
-                       // use cache if called before;
-                       if (typeof wa !== 'undefined') return wa;
-                       
-                       wa = false;
-                       
-                       if (typeof WebAssembly === 'undefined') return wa;
-                       
-                       // If WebAssenbly is disabled, code can throw on compile
-                       try {
-                               // https://github.com/brion/min-wasm-fail/blob/master/min-wasm-fail.in.js
-                               // Additional check that WA internals are correct
-                               
-                               /* eslint-disable comma-spacing, max-len */
-                               var bin      = new Uint8Array([ 0,97,115,109,1,0,0,0,1,6,1,96,1,127,1,127,3,2,1,0,5,3,1,0,1,7,8,1,4,116,101,115,116,0,0,10,16,1,14,0,32,0,65,1,54,2,0,32,0,40,2,0,11 ]);
-                               var module   = new WebAssembly.Module(bin);
-                               var instance = new WebAssembly.Instance(module, {});
-                               
-                               // test storing to and loading from a non-zero location via a parameter.
-                               // Safari on iOS 11.2.5 returns 0 unexpectedly at non-zero locations
-                               if (instance.exports.test(4) !== 0) wa = true;
-                               
-                               return wa;
-                       } catch (__) {}
-                       
-                       return wa;
-               };
-               
-       },{}],24:[function(require,module,exports){
-               /*
-               object-assign
-               (c) Sindre Sorhus
-               @license MIT
-               */
-               
-               'use strict';
-               /* eslint-disable no-unused-vars */
-               var getOwnPropertySymbols = Object.getOwnPropertySymbols;
-               var hasOwnProperty = Object.prototype.hasOwnProperty;
-               var propIsEnumerable = Object.prototype.propertyIsEnumerable;
-               
-               function toObject(val) {
-                       if (val === null || val === undefined) {
-                               throw new TypeError('Object.assign cannot be called with null or undefined');
-                       }
-                       
-                       return Object(val);
-               }
-               
-               function shouldUseNative() {
-                       try {
-                               if (!Object.assign) {
-                                       return false;
-                               }
-                               
-                               // Detect buggy property enumeration order in older V8 versions.
-                               
-                               // https://bugs.chromium.org/p/v8/issues/detail?id=4118
-                               var test1 = new String('abc');  // eslint-disable-line no-new-wrappers
-                               test1[5] = 'de';
-                               if (Object.getOwnPropertyNames(test1)[0] === '5') {
-                                       return false;
-                               }
-                               
-                               // https://bugs.chromium.org/p/v8/issues/detail?id=3056
-                               var test2 = {};
-                               for (var i = 0; i < 10; i++) {
-                                       test2['_' + String.fromCharCode(i)] = i;
-                               }
-                               var order2 = Object.getOwnPropertyNames(test2).map(function (n) {
-                                       return test2[n];
-                               });
-                               if (order2.join('') !== '0123456789') {
-                                       return false;
-                               }
-                               
-                               // https://bugs.chromium.org/p/v8/issues/detail?id=3056
-                               var test3 = {};
-                               'abcdefghijklmnopqrst'.split('').forEach(function (letter) {
-                                       test3[letter] = letter;
-                               });
-                               if (Object.keys(Object.assign({}, test3)).join('') !==
-                                       'abcdefghijklmnopqrst') {
-                                       return false;
-                               }
-                               
-                               return true;
-                       } catch (err) {
-                               // We don't expect any of the above to throw, but better to be safe.
-                               return false;
-                       }
-               }
-               
-               module.exports = shouldUseNative() ? Object.assign : function (target, source) {
-                       var from;
-                       var to = toObject(target);
-                       var symbols;
-                       
-                       for (var s = 1; s < arguments.length; s++) {
-                               from = Object(arguments[s]);
-                               
-                               for (var key in from) {
-                                       if (hasOwnProperty.call(from, key)) {
-                                               to[key] = from[key];
-                                       }
-                               }
-                               
-                               if (getOwnPropertySymbols) {
-                                       symbols = getOwnPropertySymbols(from);
-                                       for (var i = 0; i < symbols.length; i++) {
-                                               if (propIsEnumerable.call(from, symbols[i])) {
-                                                       to[symbols[i]] = from[symbols[i]];
-                                               }
-                                       }
-                               }
-                       }
-                       
-                       return to;
-               };
-               
-       },{}],25:[function(require,module,exports){
-               var bundleFn = arguments[3];
-               var sources = arguments[4];
-               var cache = arguments[5];
-               
-               var stringify = JSON.stringify;
-               
-               module.exports = function (fn, options) {
-                       var wkey;
-                       var cacheKeys = Object.keys(cache);
-                       
-                       for (var i = 0, l = cacheKeys.length; i < l; i++) {
-                               var key = cacheKeys[i];
-                               var exp = cache[key].exports;
-                               // Using babel as a transpiler to use esmodule, the export will always
-                               // be an object with the default export as a property of it. To ensure
-                               // the existing api and babel esmodule exports are both supported we
-                               // check for both
-                               if (exp === fn || exp && exp.default === fn) {
-                                       wkey = key;
-                                       break;
-                               }
-                       }
-                       
-                       if (!wkey) {
-                               wkey = Math.floor(Math.pow(16, 8) * Math.random()).toString(16);
-                               var wcache = {};
-                               for (var i = 0, l = cacheKeys.length; i < l; i++) {
-                                       var key = cacheKeys[i];
-                                       wcache[key] = key;
-                               }
-                               sources[wkey] = [
-                                       'function(require,module,exports){' + fn + '(self); }',
-                                       wcache
-                               ];
-                       }
-                       var skey = Math.floor(Math.pow(16, 8) * Math.random()).toString(16);
-                       
-                       var scache = {}; scache[wkey] = wkey;
-                       sources[skey] = [
-                               'function(require,module,exports){' +
-                               // try to call default if defined to also support babel esmodule exports
-                               'var f = require(' + stringify(wkey) + ');' +
-                               '(f.default ? f.default : f)(self);' +
-                               '}',
-                               scache
-                       ];
-                       
-                       var workerSources = {};
-                       resolveSources(skey);
-                       
-                       function resolveSources(key) {
-                               workerSources[key] = true;
-                               
-                               for (var depPath in sources[key][1]) {
-                                       var depKey = sources[key][1][depPath];
-                                       if (!workerSources[depKey]) {
-                                               resolveSources(depKey);
-                                       }
-                               }
-                       }
-                       
-                       var src = '(' + bundleFn + ')({'
-                               + Object.keys(workerSources).map(function (key) {
-                                       return stringify(key) + ':['
-                                               + sources[key][0]
-                                               + ',' + stringify(sources[key][1]) + ']'
-                                               ;
-                               }).join(',')
-                               + '},{},[' + stringify(skey) + '])'
-                       ;
-                       
-                       var URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
-                       
-                       var blob = new Blob([src], { type: 'text/javascript' });
-                       if (options && options.bare) { return blob; }
-                       var workerUrl = URL.createObjectURL(blob);
-                       var worker = new Worker(workerUrl);
-                       worker.objectURL = workerUrl;
-                       return worker;
-               };
-               
-       },{}],"/":[function(require,module,exports){
-               'use strict';
-               
-               function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }
-               
-               function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); }
-               
-               function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
-               
-               function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
-               
-               var assign = require('object-assign');
-               
-               var webworkify = require('webworkify');
-               
-               var MathLib = require('./lib/mathlib');
-               
-               var Pool = require('./lib/pool');
-               
-               var utils = require('./lib/utils');
-               
-               var worker = require('./lib/worker');
-               
-               var createStages = require('./lib/stepper');
-               
-               var createRegions = require('./lib/tiler'); // Deduplicate pools & limiters with the same configs
-// when user creates multiple pica instances.
-               
-               
-               var singletones = {};
-               var NEED_SAFARI_FIX = false;
-               
-               try {
-                       if (typeof navigator !== 'undefined' && navigator.userAgent) {
-                               NEED_SAFARI_FIX = navigator.userAgent.indexOf('Safari') >= 0;
-                       }
-               } catch (e) {}
-               
-               var concurrency = 1;
-               
-               if (typeof navigator !== 'undefined') {
-                       concurrency = Math.min(navigator.hardwareConcurrency || 1, 4);
-               }
-               
-               var DEFAULT_PICA_OPTS = {
-                       tile: 1024,
-                       concurrency: concurrency,
-                       features: ['js', 'wasm', 'ww'],
-                       idle: 2000
-               };
-               var DEFAULT_RESIZE_OPTS = {
-                       quality: 3,
-                       alpha: false,
-                       unsharpAmount: 0,
-                       unsharpRadius: 0.0,
-                       unsharpThreshold: 0
-               };
-               var CAN_NEW_IMAGE_DATA;
-               var CAN_CREATE_IMAGE_BITMAP;
-               
-               function workerFabric() {
-                       return {
-                               value: webworkify(worker),
-                               destroy: function destroy() {
-                                       this.value.terminate();
-                                       
-                                       if (typeof window !== 'undefined') {
-                                               var url = window.URL || window.webkitURL || window.mozURL || window.msURL;
-                                               
-                                               if (url && url.revokeObjectURL && this.value.objectURL) {
-                                                       url.revokeObjectURL(this.value.objectURL);
-                                               }
-                                       }
-                               }
-                       };
-               } ////////////////////////////////////////////////////////////////////////////////
-// API methods
-               
-               
-               function Pica(options) {
-                       if (!(this instanceof Pica)) return new Pica(options);
-                       this.options = assign({}, DEFAULT_PICA_OPTS, options || {});
-                       var limiter_key = "lk_".concat(this.options.concurrency); // Share limiters to avoid multiple parallel workers when user creates
-                       // multiple pica instances.
-                       
-                       this.__limit = singletones[limiter_key] || utils.limiter(this.options.concurrency);
-                       if (!singletones[limiter_key]) singletones[limiter_key] = this.__limit; // List of supported features, according to options & browser/node.js
-                       
-                       this.features = {
-                               js: false,
-                               // pure JS implementation, can be disabled for testing
-                               wasm: false,
-                               // webassembly implementation for heavy functions
-                               cib: false,
-                               // resize via createImageBitmap (only FF at this moment)
-                               ww: false // webworkers
-                               
-                       };
-                       this.__workersPool = null; // Store requested features for webworkers
-                       
-                       this.__requested_features = [];
-                       this.__mathlib = null;
-               }
-               
-               Pica.prototype.init = function () {
-                       var _this = this;
-                       
-                       if (this.__initPromise) return this.__initPromise; // Test if we can create ImageData without canvas and memory copy
-                       
-                       if (CAN_NEW_IMAGE_DATA !== false && CAN_NEW_IMAGE_DATA !== true) {
-                               CAN_NEW_IMAGE_DATA = false;
-                               
-                               if (typeof ImageData !== 'undefined' && typeof Uint8ClampedArray !== 'undefined') {
-                                       try {
-                                               /* eslint-disable no-new */
-                                               new ImageData(new Uint8ClampedArray(400), 10, 10);
-                                               CAN_NEW_IMAGE_DATA = true;
-                                       } catch (__) {}
-                               }
-                       } // ImageBitmap can be effective in 2 places:
-                       //
-                       // 1. Threaded jpeg unpack (basic)
-                       // 2. Built-in resize (blocked due problem in chrome, see issue #89)
-                       //
-                       // For basic use we also need ImageBitmap wo support .close() method,
-                       // see https://developer.mozilla.org/ru/docs/Web/API/ImageBitmap
-                       
-                       
-                       if (CAN_CREATE_IMAGE_BITMAP !== false && CAN_CREATE_IMAGE_BITMAP !== true) {
-                               CAN_CREATE_IMAGE_BITMAP = false;
-                               
-                               if (typeof ImageBitmap !== 'undefined') {
-                                       if (ImageBitmap.prototype && ImageBitmap.prototype.close) {
-                                               CAN_CREATE_IMAGE_BITMAP = true;
-                                       } else {
-                                               this.debug('ImageBitmap does not support .close(), disabled');
-                                       }
-                               }
-                       }
-                       
-                       var features = this.options.features.slice();
-                       
-                       if (features.indexOf('all') >= 0) {
-                               features = ['cib', 'wasm', 'js', 'ww'];
-                       }
-                       
-                       this.__requested_features = features;
-                       this.__mathlib = new MathLib(features); // Check WebWorker support if requested
-                       
-                       if (features.indexOf('ww') >= 0) {
-                               if (typeof window !== 'undefined' && 'Worker' in window) {
-                                       // IE <= 11 don't allow to create webworkers from string. We should check it.
-                                       // https://connect.microsoft.com/IE/feedback/details/801810/web-workers-from-blob-urls-in-ie-10-and-11
-                                       try {
-                                               var wkr = require('webworkify')(function () {});
-                                               
-                                               wkr.terminate();
-                                               this.features.ww = true; // pool uniqueness depends on pool config + webworker config
-                                               
-                                               var wpool_key = "wp_".concat(JSON.stringify(this.options));
-                                               
-                                               if (singletones[wpool_key]) {
-                                                       this.__workersPool = singletones[wpool_key];
-                                               } else {
-                                                       this.__workersPool = new Pool(workerFabric, this.options.idle);
-                                                       singletones[wpool_key] = this.__workersPool;
-                                               }
-                                       } catch (__) {}
-                               }
-                       }
-                       
-                       var initMath = this.__mathlib.init().then(function (mathlib) {
-                               // Copy detected features
-                               assign(_this.features, mathlib.features);
-                       });
-                       
-                       var checkCibResize;
-                       
-                       if (!CAN_CREATE_IMAGE_BITMAP) {
-                               checkCibResize = Promise.resolve(false);
-                       } else {
-                               checkCibResize = utils.cib_support().then(function (status) {
-                                       if (_this.features.cib && features.indexOf('cib') < 0) {
-                                               _this.debug('createImageBitmap() resize supported, but disabled by config');
-                                               
-                                               return;
-                                       }
-                                       
-                                       if (features.indexOf('cib') >= 0) _this.features.cib = status;
-                               });
-                       } // Init math lib. That's async because can load some
-                       
-                       
-                       this.__initPromise = Promise.all([initMath, checkCibResize]).then(function () {
-                               return _this;
-                       });
-                       return this.__initPromise;
-               };
-               
-               Pica.prototype.resize = function (from, to, options) {
-                       var _this2 = this;
-                       
-                       this.debug('Start resize...');
-                       var opts = assign({}, DEFAULT_RESIZE_OPTS);
-                       
-                       if (!isNaN(options)) {
-                               opts = assign(opts, {
-                                       quality: options
-                               });
-                       } else if (options) {
-                               opts = assign(opts, options);
-                       }
-                       
-                       opts.toWidth = to.width;
-                       opts.toHeight = to.height;
-                       opts.width = from.naturalWidth || from.width;
-                       opts.height = from.naturalHeight || from.height; // Prevent stepper from infinite loop
-                       
-                       if (to.width === 0 || to.height === 0) {
-                               return Promise.reject(new Error("Invalid output size: ".concat(to.width, "x").concat(to.height)));
-                       }
-                       
-                       if (opts.unsharpRadius > 2) opts.unsharpRadius = 2;
-                       var canceled = false;
-                       var cancelToken = null;
-                       
-                       if (opts.cancelToken) {
-                               // Wrap cancelToken to avoid successive resolve & set flag
-                               cancelToken = opts.cancelToken.then(function (data) {
-                                       canceled = true;
-                                       throw data;
-                               }, function (err) {
-                                       canceled = true;
-                                       throw err;
-                               });
-                       }
-                       
-                       var DEST_TILE_BORDER = 3; // Max possible filter window size
-                       
-                       var destTileBorder = Math.ceil(Math.max(DEST_TILE_BORDER, 2.5 * opts.unsharpRadius | 0));
-                       return this.init().then(function () {
-                               if (canceled) return cancelToken; // if createImageBitmap supports resize, just do it and return
-                               
-                               if (_this2.features.cib) {
-                                       var toCtx = to.getContext('2d', {
-                                               alpha: Boolean(opts.alpha)
-                                       });
-                                       
-                                       _this2.debug('Resize via createImageBitmap()');
-                                       
-                                       return createImageBitmap(from, {
-                                               resizeWidth: opts.toWidth,
-                                               resizeHeight: opts.toHeight,
-                                               resizeQuality: utils.cib_quality_name(opts.quality)
-                                       }).then(function (imageBitmap) {
-                                               if (canceled) return cancelToken; // if no unsharp - draw directly to output canvas
-                                               
-                                               if (!opts.unsharpAmount) {
-                                                       toCtx.drawImage(imageBitmap, 0, 0);
-                                                       imageBitmap.close();
-                                                       toCtx = null;
-                                                       
-                                                       _this2.debug('Finished!');
-                                                       
-                                                       return to;
-                                               }
-                                               
-                                               _this2.debug('Unsharp result');
-                                               
-                                               var tmpCanvas = document.createElement('canvas');
-                                               tmpCanvas.width = opts.toWidth;
-                                               tmpCanvas.height = opts.toHeight;
-                                               var tmpCtx = tmpCanvas.getContext('2d', {
-                                                       alpha: Boolean(opts.alpha)
-                                               });
-                                               tmpCtx.drawImage(imageBitmap, 0, 0);
-                                               imageBitmap.close();
-                                               var iData = tmpCtx.getImageData(0, 0, opts.toWidth, opts.toHeight);
-                                               
-                                               _this2.__mathlib.unsharp(iData.data, opts.toWidth, opts.toHeight, opts.unsharpAmount, opts.unsharpRadius, opts.unsharpThreshold);
-                                               
-                                               toCtx.putImageData(iData, 0, 0);
-                                               iData = tmpCtx = tmpCanvas = toCtx = null;
-                                               
-                                               _this2.debug('Finished!');
-                                               
-                                               return to;
-                                       });
-                               } //
-                               // No easy way, let's resize manually via arrays
-                               //
-                               // Share cache between calls:
-                               //
-                               // - wasm instance
-                               // - wasm memory object
-                               //
-                               
-                               
-                               var cache = {}; // Call resizer in webworker or locally, depending on config
-                               
-                               var invokeResize = function invokeResize(opts) {
-                                       return Promise.resolve().then(function () {
-                                               if (!_this2.features.ww) return _this2.__mathlib.resizeAndUnsharp(opts, cache);
-                                               return new Promise(function (resolve, reject) {
-                                                       var w = _this2.__workersPool.acquire();
-                                                       
-                                                       if (cancelToken) cancelToken.catch(function (err) {
-                                                               return reject(err);
-                                                       });
-                                                       
-                                                       w.value.onmessage = function (ev) {
-                                                               w.release();
-                                                               if (ev.data.err) reject(ev.data.err);else resolve(ev.data.result);
-                                                       };
-                                                       
-                                                       w.value.postMessage({
-                                                               opts: opts,
-                                                               features: _this2.__requested_features,
-                                                               preload: {
-                                                                       wasm_nodule: _this2.__mathlib.__
-                                                               }
-                                                       }, [opts.src.buffer]);
-                                               });
-                                       });
-                               };
-                               
-                               var tileAndResize = function tileAndResize(from, to, opts) {
-                                       var srcCtx;
-                                       var srcImageBitmap;
-                                       var toCtx;
-                                       
-                                       var processTile = function processTile(tile) {
-                                               return _this2.__limit(function () {
-                                                       if (canceled) return cancelToken;
-                                                       var srcImageData; // Extract tile RGBA buffer, depending on input type
-                                                       
-                                                       if (utils.isCanvas(from)) {
-                                                               _this2.debug('Get tile pixel data'); // If input is Canvas - extract region data directly
-                                                               
-                                                               
-                                                               srcImageData = srcCtx.getImageData(tile.x, tile.y, tile.width, tile.height);
-                                                       } else {
-                                                               // If input is Image or decoded to ImageBitmap,
-                                                               // draw region to temporary canvas and extract data from it
-                                                               //
-                                                               // Note! Attempt to reuse this canvas causes significant slowdown in chrome
-                                                               //
-                                                               _this2.debug('Draw tile imageBitmap/image to temporary canvas');
-                                                               
-                                                               var tmpCanvas = document.createElement('canvas');
-                                                               tmpCanvas.width = tile.width;
-                                                               tmpCanvas.height = tile.height;
-                                                               var tmpCtx = tmpCanvas.getContext('2d', {
-                                                                       alpha: Boolean(opts.alpha)
-                                                               });
-                                                               tmpCtx.globalCompositeOperation = 'copy';
-                                                               tmpCtx.drawImage(srcImageBitmap || from, tile.x, tile.y, tile.width, tile.height, 0, 0, tile.width, tile.height);
-                                                               
-                                                               _this2.debug('Get tile pixel data');
-                                                               
-                                                               srcImageData = tmpCtx.getImageData(0, 0, tile.width, tile.height);
-                                                               tmpCtx = tmpCanvas = null;
-                                                       }
-                                                       
-                                                       var o = {
-                                                               src: srcImageData.data,
-                                                               width: tile.width,
-                                                               height: tile.height,
-                                                               toWidth: tile.toWidth,
-                                                               toHeight: tile.toHeight,
-                                                               scaleX: tile.scaleX,
-                                                               scaleY: tile.scaleY,
-                                                               offsetX: tile.offsetX,
-                                                               offsetY: tile.offsetY,
-                                                               quality: opts.quality,
-                                                               alpha: opts.alpha,
-                                                               unsharpAmount: opts.unsharpAmount,
-                                                               unsharpRadius: opts.unsharpRadius,
-                                                               unsharpThreshold: opts.unsharpThreshold
-                                                       };
-                                                       
-                                                       _this2.debug('Invoke resize math');
-                                                       
-                                                       return Promise.resolve().then(function () {
-                                                               return invokeResize(o);
-                                                       }).then(function (result) {
-                                                               if (canceled) return cancelToken;
-                                                               srcImageData = null;
-                                                               var toImageData;
-                                                               
-                                                               _this2.debug('Convert raw rgba tile result to ImageData');
-                                                               
-                                                               if (CAN_NEW_IMAGE_DATA) {
-                                                                       // this branch is for modern browsers
-                                                                       // If `new ImageData()` & Uint8ClampedArray suported
-                                                                       toImageData = new ImageData(new Uint8ClampedArray(result), tile.toWidth, tile.toHeight);
-                                                               } else {
-                                                                       // fallback for `node-canvas` and old browsers
-                                                                       // (IE11 has ImageData but does not support `new ImageData()`)
-                                                                       toImageData = toCtx.createImageData(tile.toWidth, tile.toHeight);
-                                                                       
-                                                                       if (toImageData.data.set) {
-                                                                               toImageData.data.set(result);
-                                                                       } else {
-                                                                               // IE9 don't have `.set()`
-                                                                               for (var i = toImageData.data.length - 1; i >= 0; i--) {
-                                                                                       toImageData.data[i] = result[i];
-                                                                               }
-                                                                       }
-                                                               }
-                                                               
-                                                               _this2.debug('Draw tile');
-                                                               
-                                                               if (NEED_SAFARI_FIX) {
-                                                                       // Safari draws thin white stripes between tiles without this fix
-                                                                       toCtx.putImageData(toImageData, tile.toX, tile.toY, tile.toInnerX - tile.toX, tile.toInnerY - tile.toY, tile.toInnerWidth + 1e-5, tile.toInnerHeight + 1e-5);
-                                                               } else {
-                                                                       toCtx.putImageData(toImageData, tile.toX, tile.toY, tile.toInnerX - tile.toX, tile.toInnerY - tile.toY, tile.toInnerWidth, tile.toInnerHeight);
-                                                               }
-                                                               
-                                                               return null;
-                                                       });
-                                               });
-                                       }; // Need to normalize data source first. It can be canvas or image.
-                                       // If image - try to decode in background if possible
-                                       
-                                       
-                                       return Promise.resolve().then(function () {
-                                               toCtx = to.getContext('2d', {
-                                                       alpha: Boolean(opts.alpha)
-                                               });
-                                               
-                                               if (utils.isCanvas(from)) {
-                                                       srcCtx = from.getContext('2d', {
-                                                               alpha: Boolean(opts.alpha)
-                                                       });
-                                                       return null;
-                                               }
-                                               
-                                               if (utils.isImage(from)) {
-                                                       // try do decode image in background for faster next operations
-                                                       if (!CAN_CREATE_IMAGE_BITMAP) return null;
-                                                       
-                                                       _this2.debug('Decode image via createImageBitmap');
-                                                       
-                                                       return createImageBitmap(from).then(function (imageBitmap) {
-                                                               srcImageBitmap = imageBitmap;
-                                                       });
-                                               }
-                                               
-                                               throw new Error('".from" should be image or canvas');
-                                       }).then(function () {
-                                               if (canceled) return cancelToken;
-                                               
-                                               _this2.debug('Calculate tiles'); //
-                                               // Here we are with "normalized" source,
-                                               // follow to tiling
-                                               //
-                                               
-                                               
-                                               var regions = createRegions({
-                                                       width: opts.width,
-                                                       height: opts.height,
-                                                       srcTileSize: _this2.options.tile,
-                                                       toWidth: opts.toWidth,
-                                                       toHeight: opts.toHeight,
-                                                       destTileBorder: destTileBorder
-                                               });
-                                               var jobs = regions.map(function (tile) {
-                                                       return processTile(tile);
-                                               });
-                                               
-                                               function cleanup() {
-                                                       if (srcImageBitmap) {
-                                                               srcImageBitmap.close();
-                                                               srcImageBitmap = null;
-                                                       }
-                                               }
-                                               
-                                               _this2.debug('Process tiles');
-                                               
-                                               return Promise.all(jobs).then(function () {
-                                                       _this2.debug('Finished!');
-                                                       
-                                                       cleanup();
-                                                       return to;
-                                               }, function (err) {
-                                                       cleanup();
-                                                       throw err;
-                                               });
-                                       });
-                               };
-                               
-                               var processStages = function processStages(stages, from, to, opts) {
-                                       if (canceled) return cancelToken;
-                                       
-                                       var _stages$shift = stages.shift(),
-                                               _stages$shift2 = _slicedToArray(_stages$shift, 2),
-                                               toWidth = _stages$shift2[0],
-                                               toHeight = _stages$shift2[1];
-                                       
-                                       var isLastStage = stages.length === 0;
-                                       opts = assign({}, opts, {
-                                               toWidth: toWidth,
-                                               toHeight: toHeight,
-                                               // only use user-defined quality for the last stage,
-                                               // use simpler (Hamming) filter for the first stages where
-                                               // scale factor is large enough (more than 2-3)
-                                               quality: isLastStage ? opts.quality : Math.min(1, opts.quality)
-                                       });
-                                       var tmpCanvas;
-                                       
-                                       if (!isLastStage) {
-                                               // create temporary canvas
-                                               tmpCanvas = document.createElement('canvas');
-                                               tmpCanvas.width = toWidth;
-                                               tmpCanvas.height = toHeight;
-                                       }
-                                       
-                                       return tileAndResize(from, isLastStage ? to : tmpCanvas, opts).then(function () {
-                                               if (isLastStage) return to;
-                                               opts.width = toWidth;
-                                               opts.height = toHeight;
-                                               return processStages(stages, tmpCanvas, to, opts);
-                                       });
-                               };
-                               
-                               var stages = createStages(opts.width, opts.height, opts.toWidth, opts.toHeight, _this2.options.tile, destTileBorder);
-                               return processStages(stages, from, to, opts);
-                       });
-               }; // RGBA buffer resize
-//
-               
-               
-               Pica.prototype.resizeBuffer = function (options) {
-                       var _this3 = this;
-                       
-                       var opts = assign({}, DEFAULT_RESIZE_OPTS, options);
-                       return this.init().then(function () {
-                               return _this3.__mathlib.resizeAndUnsharp(opts);
-                       });
-               };
-               
-               Pica.prototype.toBlob = function (canvas, mimeType, quality) {
-                       mimeType = mimeType || 'image/png';
-                       return new Promise(function (resolve) {
-                               if (canvas.toBlob) {
-                                       canvas.toBlob(function (blob) {
-                                               return resolve(blob);
-                                       }, mimeType, quality);
-                                       return;
-                               } // Fallback for old browsers
-                               
-                               
-                               var asString = atob(canvas.toDataURL(mimeType, quality).split(',')[1]);
-                               var len = asString.length;
-                               var asBuffer = new Uint8Array(len);
-                               
-                               for (var i = 0; i < len; i++) {
-                                       asBuffer[i] = asString.charCodeAt(i);
-                               }
-                               
-                               resolve(new Blob([asBuffer], {
-                                       type: mimeType
-                               }));
-                       });
-               };
-               
-               Pica.prototype.debug = function () {};
-               
-               module.exports = Pica;
-               
-       },{"./lib/mathlib":1,"./lib/pool":9,"./lib/stepper":10,"./lib/tiler":11,"./lib/utils":12,"./lib/worker":13,"object-assign":24,"webworkify":25}]},{},[])("/")
-});
-
-/**
- * This module allows resizing and conversion of HTMLImageElements to Blob and File objects
- *
- * @author     Maximilian Mader
- * @copyright  2001-2018 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Image/Resizer
- */
-define('WoltLabSuite/Core/Image/Resizer',[
-       'WoltLabSuite/Core/FileUtil',
-       'WoltLabSuite/Core/Image/ExifUtil',
-       'Pica'
-], function(FileUtil, ExifUtil, Pica) {
-       "use strict";
-       
-       var pica = new Pica({features: ['js', 'wasm', 'ww']});
-       
-       /**
-        * @constructor
-        */
-       function ImageResizer() { }
-       ImageResizer.prototype = {
-               maxWidth: 800,
-               maxHeight: 600,
-               quality: 0.8,
-               fileType: 'image/jpeg',
-               
-               /**
-                * Sets the default maximum width for this instance
-                *
-                * @param       {Number}        value   the new default maximum width
-                * @returns     {ImageResizer}          this ImageResizer instance
-                */
-               setMaxWidth: function (value) {
-                       if (value == null) value = ImageResizer.prototype.maxWidth;
-                       
-                       this.maxWidth = value;
-                       return this;
-               },
-               
-               /**
-                * Sets the default maximum height for this instance
-                *
-                * @param       {Number}        value   the new default maximum height
-                * @returns     {ImageResizer}          this ImageResizer instance
-                */
-               setMaxHeight: function (value) {
-                       if (value == null) value = ImageResizer.prototype.maxHeight;
-                       
-                       this.maxHeight = value;
-                       return this;
-               },
-               
-               /**
-                * Sets the default quality for this instance
-                *
-                * @param       {Number}        value   the new default quality
-                * @returns     {ImageResizer}          this ImageResizer instance
-                */
-               setQuality: function (value) {
-                       if (value == null) value = ImageResizer.prototype.quality;
-                       
-                       this.quality = value;
-                       return this;
-               },
-               
-               /**
-                * Sets the default file type for this instance
-                *
-                * @param       {Number}        value   the new default file type
-                * @returns     {ImageResizer}          this ImageResizer instance
-                */
-               setFileType: function (value) {
-                       if (value == null) value = ImageResizer.prototype.fileType;
-                       
-                       this.fileType = value;
-                       return this;
-               },
-               
-               /**
-                * Converts the given object of exif data and image data into a File.
-                *
-                * @param       {Object{exif: Uint8Array|undefined, image: Canvas} data  object containing exif data and image data
-                * @param       {String}        fileName        the name of the returned file
-                * @param       {String}        [fileType]      the type of the returned image
-                * @param       {Number}        [quality]       quality setting, currently only effective for "image/jpeg"
-                * @returns     {Promise<File>} the File object
-                */
-               saveFile: function (data, fileName, fileType, quality) {
-                       fileType = fileType || this.fileType;
-                       quality = quality || this.quality;
-                       
-                       var basename = fileName.match(/(.+)(\..+?)$/);
-                       
-                       return pica.toBlob(data.image, fileType, quality)
-                               .then(function (blob) {
-                                       if (fileType === 'image/jpeg' && typeof data.exif !== 'undefined') {
-                                               return ExifUtil.setExifData(blob, data.exif);
-                                       }
-                                       
-                                       return blob;
-                               })
-                               .then(function (blob) {
-                                       return FileUtil.blobToFile(blob, basename[1] + '_autoscaled');
-                               });
-               },
-               
-               /**
-                * Loads the given file into an image object and parses Exif information.
-                * 
-                * @param   {File}    file the file to load
-                * @returns {Promise} resulting image data
-                */
-               loadFile: function (file) {
-                       var exif = undefined;
-                       if (file.type === 'image/jpeg') {
-                               // Extract EXIF data
-                               exif = ExifUtil.getExifBytesFromJpeg(file);
-                       }
-                       
-                       var loader = new Promise(function (resolve, reject) {
-                               var reader = new FileReader();
-                               var image = new Image();
-                               
-                               reader.addEventListener('load', function () {
-                                       image.src = reader.result;
-                               });
-                               
-                               reader.addEventListener('error', function () {
-                                       reader.abort();
-                                       reject(reader.error);
-                               });
-                               
-                               image.addEventListener('error', reject);
-                               
-                               image.addEventListener('load', function () {
-                                       resolve(image);
-                               });
-                               
-                               reader.readAsDataURL(file);
-                       });
-                       
-                       return Promise.all([ exif, loader ])
-                               .then(function (result) {
-                                       return { exif: result[0], image: result[1] };
-                               });
-               },
-               
-               /**
-                * Downscales an image given as File object.
-                *
-                * @param       {Image}       image             the image to resize
-                * @param       {Number}      [maxWidth]        maximum width
-                * @param       {Number}      [maxHeight]       maximum height
-                * @param       {Number}      [quality]         quality in percent
-                * @param       {boolean}     [force]           whether to force scaling even if unneeded (thus re-encoding with a possibly smaller file size)
-                * @param       {Promise}     cancelPromise     a Promise used to cancel pica's operation when it resolves
-                * @returns     {Promise<Blob | undefined>}     a Promise resolving with the resized image as a {Canvas} or undefined if no resizing happened
-                */
-               resize: function (image, maxWidth, maxHeight, quality, force, cancelPromise) {
-                       maxWidth = maxWidth || this.maxWidth;
-                       maxHeight = maxHeight || this.maxHeight;
-                       quality = quality || this.quality;
-                       force = force || false;
-                       
-                       var canvas = document.createElement('canvas');
-                       
-                       // Prevent upscaling
-                       var newWidth = Math.min(maxWidth, image.width);
-                       var newHeight = Math.min(maxHeight, image.height);
-                       
-                       if (image.width <= newWidth && image.height <= newHeight && !force) {
-                               return Promise.resolve(undefined);
-                       }
-                       
-                       // Keep image ratio
-                       var ratio = Math.min(newWidth / image.width, newHeight / image.height);
-                       canvas.width = Math.floor(image.width * ratio);
-                       canvas.height = Math.floor(image.height * ratio);
-                       
-                       // Map to Pica's quality
-                       var resizeQuality = 1;
-                       if (quality >= 0.8) {
-                               resizeQuality = 3;
-                       }
-                       else if (quality >= 0.4) {
-                               resizeQuality = 2;
-                       }
-                       
-                       var options = {
-                               quality: resizeQuality,
-                               cancelToken: cancelPromise,
-                               alpha: true
-                       };
-                       
-                       return pica.resize(image, canvas, options);
-               }
-       };
-       
-       return ImageResizer;
-});
-
-/**
- * Dropdown language chooser.
- * 
- * @author     Alexander Ebert, Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Language/Chooser
- */
-define('WoltLabSuite/Core/Language/Chooser',['Dictionary', 'Language', 'Dom/Traverse', 'Dom/Util', 'ObjectMap', 'Ui/SimpleDropdown'], function(Dictionary, Language, DomTraverse, DomUtil, ObjectMap, UiSimpleDropdown) {
-       "use strict";
-       
-       var _choosers = new Dictionary();
-       var _didInit = false;
-       var _forms = new ObjectMap();
-       
-       var _callbackSubmit = null;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Language/Chooser
-        */
-       return {
-               /**
-                * Initializes a language chooser.
-                * 
-                * @param       {string}                                containerId             input element container id
-                * @param       {string}                                chooserId               input element id
-                * @param       {int}                                   languageId              selected language id
-                * @param       {object<int, object<string, string>>}   languages               data of available languages
-                * @param       {function}                              callback                function called after a language is selected
-                * @param       {boolean}                               allowEmptyValue         true if no language may be selected
-                */
-               init: function(containerId, chooserId, languageId, languages, callback, allowEmptyValue) {
-                       if (_choosers.has(chooserId)) {
-                               return;
-                       }
-                       
-                       var container = elById(containerId);
-                       if (container === null) {
-                               throw new Error("Expected a valid container id, cannot find '" + chooserId + "'.");
-                       }
-                       
-                       var element = elById(chooserId);
-                       if (element === null) {
-                               element = elCreate('input');
-                               elAttr(element, 'type', 'hidden');
-                               elAttr(element, 'id', chooserId);
-                               elAttr(element, 'name', chooserId);
-                               elAttr(element, 'value', languageId);
-                               
-                               container.appendChild(element);
-                       }
-                       
-                       this._initElement(chooserId, element, languageId, languages, callback, allowEmptyValue);
-               },
-               
-               /**
-                * Caches common event listener callbacks.
-                */
-               _setup: function() {
-                       if (_didInit) return;
-                       _didInit = true;
-                       
-                       _callbackSubmit = this._submit.bind(this);
-               },
-               
-               /**
-                * Sets up DOM and event listeners for a language chooser.
-                *
-                * @param       {string}                                chooserId               chooser id
-                * @param       {Element}                               element                 chooser element
-                * @param       {int}                                   languageId              selected language id
-                * @param       {object<int, object<string, string>>}   languages               data of available languages
-                * @param       {function}                              callback                callback function invoked on selection change
-                * @param       {boolean}                               allowEmptyValue         true if no language may be selected
-                */
-               _initElement: function(chooserId, element, languageId, languages, callback, allowEmptyValue) {
-                       var container;
-                       
-                       if (element.parentNode.nodeName === 'DD') {
-                               container = elCreate('div');
-                               container.className = 'dropdown';
-                               
-                               // language chooser is the first child so that descriptions and error messages
-                               // are always shown below the language chooser
-                               DomUtil.prepend(container, element.parentNode);
-                       }
-                       else {
-                               container = element.parentNode;
-                               container.classList.add('dropdown');
-                       }
-                       
-                       elHide(element);
-                       
-                       var dropdownToggle = elCreate('a');
-                       dropdownToggle.className = 'dropdownToggle dropdownIndicator boxFlag box24 inputPrefix' + (element.parentNode.nodeName === 'DD' ? ' button' : '');
-                       container.appendChild(dropdownToggle);
-                       
-                       var dropdownMenu = elCreate('ul');
-                       dropdownMenu.className = 'dropdownMenu';
-                       container.appendChild(dropdownMenu);
-                       
-                       var callbackClick = (function(event) {
-                               var languageId = ~~elData(event.currentTarget, 'language-id');
-                               
-                               var activeItem = DomTraverse.childByClass(dropdownMenu, 'active');
-                               if (activeItem !== null) activeItem.classList.remove('active');
-                               
-                               if (languageId) event.currentTarget.classList.add('active');
-                               
-                               this._select(chooserId, languageId, event.currentTarget);
-                       }).bind(this);
-                       
-                       // add language dropdown items
-                       var link, img, listItem, span;
-                       for (var availableLanguageId in languages) {
-                               if (languages.hasOwnProperty(availableLanguageId)) {
-                                       var language = languages[availableLanguageId];
-                                       
-                                       listItem = elCreate('li');
-                                       listItem.className = 'boxFlag';
-                                       listItem.addEventListener(WCF_CLICK_EVENT, callbackClick);
-                                       elData(listItem, 'language-id', availableLanguageId);
-                                       if (language.languageCode !== undefined) elData(listItem, 'language-code', language.languageCode);
-                                       dropdownMenu.appendChild(listItem);
-                                       
-                                       link = elCreate('a');
-                                       link.className = 'box24';
-                                       listItem.appendChild(link);
-                                       
-                                       img = elCreate('img');
-                                       elAttr(img, 'src', language.iconPath);
-                                       elAttr(img, 'alt', '');
-                                       img.className = 'iconFlag';
-                                       link.appendChild(img);
-                                       
-                                       span = elCreate('span');
-                                       span.textContent = language.languageName;
-                                       link.appendChild(span);
-                                       
-                                       if (availableLanguageId == languageId) {
-                                               dropdownToggle.innerHTML = listItem.firstChild.innerHTML;
-                                       }
-                               }
-                       }
-                       
-                       // add dropdown item for "no selection"
-                       if (allowEmptyValue) {
-                               listItem = elCreate('li');
-                               listItem.className = 'dropdownDivider';
-                               dropdownMenu.appendChild(listItem);
-                               
-                               listItem = elCreate('li');
-                               elData(listItem, 'language-id', 0);
-                               listItem.addEventListener(WCF_CLICK_EVENT, callbackClick);
-                               dropdownMenu.appendChild(listItem);
-                               
-                               link = elCreate('a');
-                               link.textContent = Language.get('wcf.global.language.noSelection');
-                               listItem.appendChild(link);
-                               
-                               if (languageId === 0) {
-                                       dropdownToggle.innerHTML = listItem.firstChild.innerHTML;
-                               }
-                               
-                               listItem.addEventListener(WCF_CLICK_EVENT, callbackClick);
-                       }
-                       else if (languageId === 0) {
-                               dropdownToggle.innerHTML = null;
-                               
-                               var div = elCreate('div');
-                               dropdownToggle.appendChild(div);
-                               
-                               span = elCreate('span');
-                               span.className = 'icon icon24 fa-question';
-                               div.appendChild(span);
-                               
-                               span = elCreate('span');
-                               span.textContent = Language.get('wcf.global.language.noSelection');
-                               div.appendChild(span);
-                       }
-                       
-                       UiSimpleDropdown.init(dropdownToggle);
-                       
-                       _choosers.set(chooserId, {
-                               callback: callback,
-                               dropdownMenu: dropdownMenu,
-                               dropdownToggle: dropdownToggle,
-                               element: element
-                       });
-                       
-                       // bind to submit event
-                       var form = DomTraverse.parentByTag(element, 'FORM');
-                       if (form !== null) {
-                               form.addEventListener('submit', _callbackSubmit);
-                               
-                               var chooserIds = _forms.get(form);
-                               if (chooserIds === undefined) {
-                                       chooserIds = [];
-                                       _forms.set(form, chooserIds);
-                               }
-                               
-                               chooserIds.push(chooserId);
-                       }
-               },
-               
-               /**
-                * Selects a language from the dropdown list.
-                * 
-                * @param       {string}        chooserId       input element id
-                * @param       {int}           languageId      language id or `0` to disable i18n
-                * @param       {Element=}      listItem        selected list item
-                */
-               _select: function(chooserId, languageId, listItem) {
-                       var chooser = _choosers.get(chooserId);
-                       
-                       if (listItem === undefined) {
-                               var listItems = chooser.dropdownMenu.childNodes;
-                               for (var i = 0, length = listItems.length; i < length; i++) {
-                                       var _listItem = listItems[i];
-                                       if (~~elData(_listItem, 'language-id') === languageId) {
-                                               listItem = _listItem;
-                                               break;
-                                       }
-                               }
-                               
-                               if (listItem === undefined) {
-                                       throw new Error("Cannot select unknown language id '" + languageId + "'");
-                               }
-                       }
-                       
-                       chooser.element.value = languageId;
-                       
-                       chooser.dropdownToggle.innerHTML = listItem.firstChild.innerHTML;
-                       
-                       _choosers.set(chooserId, chooser);
-                       
-                       // execute callback
-                       if (typeof chooser.callback === 'function') {
-                               chooser.callback(listItem);
-                       }
-               },
-               
-               /**
-                * Inserts hidden fields for the language chooser value on submit.
-                *
-                * @param       {object}        event           event object
-                */
-               _submit: function(event) {
-                       var elementIds = _forms.get(event.currentTarget);
-                       
-                       var input;
-                       for (var i = 0, length = elementIds.length; i < length; i++) {
-                               input = elCreate('input');
-                               input.type = 'hidden';
-                               input.name = elementIds[i];
-                               input.value = this.getLanguageId(elementIds[i]);
-                               
-                               event.currentTarget.appendChild(input);
-                       }
-               },
-               
-               /**
-                * Returns the chooser for an input field.
-                * 
-                * @param       {string}        chooserId       input element id
-                * @return      {Dictionary}    data of the chooser
-                */
-               getChooser: function(chooserId) {
-                       var chooser = _choosers.get(chooserId);
-                       if (chooser === undefined) {
-                               throw new Error("Expected a valid language chooser input element, '" + chooserId + "' is not i18n input field.");
-                       }
-                       
-                       return chooser;
-               },
-               
-               /**
-                * Returns the selected language for a certain chooser.
-                * 
-                * @param       {string}        chooserId       input element id
-                * @return      {int}           chosen language id
-                */
-               getLanguageId: function(chooserId) {
-                       return ~~this.getChooser(chooserId).element.value;
-               },
-               
-               /**
-                * Removes the chooser with given id.
-                * 
-                * @param       {string}        chooserId       input element id
-                */
-               removeChooser: function(chooserId) {
-                       if (_choosers.has(chooserId)) {
-                               _choosers.delete(chooserId);
-                       }
-               },
-               
-               /**
-                * Sets the language for a certain chooser.
-                * 
-                * @param       {string}        chooserId       input element id
-                * @param       {int}           languageId      language id to be set
-                */
-               setLanguageId: function(chooserId, languageId) {
-                       if (_choosers.get(chooserId) === undefined) {
-                               throw new Error("Expected a valid  input element, '" + chooserId + "' is not i18n input field.");
-                       }
-                       
-                       this._select(chooserId, languageId);
-               }
-       };
-});
-
-/**
- * I18n interface for input and textarea fields.
- * 
- * @author      Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module      WoltLabSuite/Core/Language/Input
- */
-define('WoltLabSuite/Core/Language/Input',['Core', 'Dictionary', 'Language', 'ObjectMap', 'StringUtil', 'Dom/Traverse', 'Dom/Util', 'Ui/SimpleDropdown'], function(Core, Dictionary, Language, ObjectMap, StringUtil, DomTraverse, DomUtil, UiSimpleDropdown) {
-       "use strict";
-       
-       var _elements = new Dictionary();
-       var _didInit = false;
-       var _forms = new ObjectMap();
-       var _values = new Dictionary();
-       
-       var _callbackDropdownToggle = null;
-       var _callbackSubmit = null;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Language/Input
-        */
-       return {
-               /**
-                * Initializes an input field.
-                * 
-                * @param       {string}        elementId               input element id
-                * @param       {Object}        values                  preset values per language id
-                * @param       {Object}        availableLanguages      language names per language id
-                * @param       {boolean}       forceSelection          require i18n input
-                */
-               init: function(elementId, values, availableLanguages, forceSelection) {
-                       if (_values.has(elementId)) {
-                               return;
-                       }
-                       
-                       var element = elById(elementId);
-                       if (element === null) {
-                               throw new Error("Expected a valid element id, cannot find '" + elementId + "'.");
-                       }
-                       
-                       this._setup();
-                       
-                       // unescape values
-                       var unescapedValues = new Dictionary();
-                       for (var key in values) {
-                               if (values.hasOwnProperty(key)) {
-                                       unescapedValues.set(~~key, StringUtil.unescapeHTML(values[key]));
-                               }
-                       }
-                       
-                       _values.set(elementId, unescapedValues);
-                       
-                       this._initElement(elementId, element, unescapedValues, availableLanguages, forceSelection);
-               },
-               
-               /**
-                * Registers a callback for an element.
-                * 
-                * @param       {string}        elementId
-                * @param       {string}        eventName
-                * @param       {function}      callback
-                */
-               registerCallback: function (elementId, eventName, callback) {
-                       if (!_values.has(elementId)) {
-                               throw new Error("Unknown element id '" + elementId + "'.");
-                       }
-                       
-                       _elements.get(elementId).callbacks.set(eventName, callback);
-               },
-               
-               /**
-                * Unregisters the element with the given id.
-                * 
-                * @param       {string}        elementId
-                * @since       5.2
-                */
-               unregister: function(elementId) {
-                       if (!_values.has(elementId)) {
-                               throw new Error("Unknown element id '" + elementId + "'.");
-                       }
-                       
-                       _values.delete(elementId);
-                       _elements.delete(elementId);
-               },
-               
-               /**
-                * Caches common event listener callbacks.
-                */
-               _setup: function() {
-                       if (_didInit) return;
-                       _didInit = true;
-                       
-                       _callbackDropdownToggle = this._dropdownToggle.bind(this);
-                       _callbackSubmit = this._submit.bind(this);
-               },
-               
-               /**
-                * Sets up DOM and event listeners for an input field.
-                * 
-                * @param       {string}        elementId               input element id
-                * @param       {Element}       element                 input or textarea element
-                * @param       {Dictionary}    values                  preset values per language id
-                * @param       {Object}        availableLanguages      language names per language id
-                * @param       {boolean}       forceSelection          require i18n input
-                */
-               _initElement: function(elementId, element, values, availableLanguages, forceSelection) {
-                       var container = element.parentNode;
-                       if (!container.classList.contains('inputAddon')) {
-                               container = elCreate('div');
-                               container.className = 'inputAddon' + (element.nodeName === 'TEXTAREA' ? ' inputAddonTextarea' : '');
-                               //noinspection JSCheckFunctionSignatures
-                               elData(container, 'input-id', elementId);
-                               
-                               var hasFocus = document.activeElement === element;
-                               
-                               // DOM manipulation causes focused element to lose focus
-                               element.parentNode.insertBefore(container, element);
-                               container.appendChild(element);
-                               
-                               if (hasFocus) {
-                                       element.focus();
-                               }
-                       }
-                       
-                       container.classList.add('dropdown');
-                       var button = elCreate('span');
-                       button.className = 'button dropdownToggle inputPrefix';
-                       
-                       var span = elCreate('span');
-                       span.textContent = Language.get('wcf.global.button.disabledI18n');
-                       
-                       button.appendChild(span);
-                       container.insertBefore(button, element);
-                       
-                       var dropdownMenu = elCreate('ul');
-                       dropdownMenu.className = 'dropdownMenu';
-                       DomUtil.insertAfter(dropdownMenu, button);
-                       
-                       var callbackClick = (function(event, isInit) {
-                               var languageId = ~~elData(event.currentTarget, 'language-id');
-                               
-                               var activeItem = DomTraverse.childByClass(dropdownMenu, 'active');
-                               if (activeItem !== null) activeItem.classList.remove('active');
-                               
-                               if (languageId) event.currentTarget.classList.add('active');
-                               
-                               this._select(elementId, languageId, isInit || false);
-                       }).bind(this);
-                       
-                       // build language dropdown
-                       var listItem;
-                       for (var languageId in availableLanguages) {
-                               if (availableLanguages.hasOwnProperty(languageId)) {
-                                       listItem = elCreate('li');
-                                       elData(listItem, 'language-id', languageId);
-                                       
-                                       span = elCreate('span');
-                                       span.textContent = availableLanguages[languageId];
-                                       
-                                       listItem.appendChild(span);
-                                       listItem.addEventListener(WCF_CLICK_EVENT, callbackClick);
-                                       dropdownMenu.appendChild(listItem);
-                               }
-                       }
-                       
-                       if (forceSelection !== true) {
-                               listItem = elCreate('li');
-                               listItem.className = 'dropdownDivider';
-                               dropdownMenu.appendChild(listItem);
-                               
-                               listItem = elCreate('li');
-                               elData(listItem, 'language-id', 0);
-                               span = elCreate('span');
-                               span.textContent = Language.get('wcf.global.button.disabledI18n');
-                               listItem.appendChild(span);
-                               listItem.addEventListener(WCF_CLICK_EVENT, callbackClick);
-                               dropdownMenu.appendChild(listItem);
-                       }
-                       
-                       var activeItem = null;
-                       if (forceSelection === true || values.size) {
-                               for (var i = 0, length = dropdownMenu.childElementCount; i < length; i++) {
-                                       //noinspection JSUnresolvedVariable
-                                       if (~~elData(dropdownMenu.children[i], 'language-id') === LANGUAGE_ID) {
-                                               activeItem = dropdownMenu.children[i];
-                                               break;
-                                       }
-                               }
-                       }
-                       
-                       UiSimpleDropdown.init(button);
-                       UiSimpleDropdown.registerCallback(container.id, _callbackDropdownToggle);
-                       
-                       _elements.set(elementId, {
-                               buttonLabel: button.children[0],
-                               callbacks: new Dictionary(),
-                               element: element,
-                               languageId: 0,
-                               isEnabled: true,
-                               forceSelection: forceSelection
-                       });
-                       
-                       // bind to submit event
-                       var submit = DomTraverse.parentByTag(element, 'FORM');
-                       if (submit !== null) {
-                               submit.addEventListener('submit', _callbackSubmit);
-                               
-                               var elementIds = _forms.get(submit);
-                               if (elementIds === undefined) {
-                                       elementIds = [];
-                                       _forms.set(submit, elementIds);
-                               }
-                               
-                               elementIds.push(elementId);
-                       }
-                       
-                       if (activeItem !== null) {
-                               callbackClick({ currentTarget: activeItem }, true);
-                       }
-               },
-               
-               /**
-                * Selects a language or non-i18n from the dropdown list.
-                * 
-                * @param       {string}        elementId       input element id
-                * @param       {int}           languageId      language id or `0` to disable i18n
-                * @param       {boolean}       isInit          triggers pre-selection on init
-                */
-               _select: function(elementId, languageId, isInit) {
-                       var data = _elements.get(elementId);
-                       
-                       var dropdownMenu = UiSimpleDropdown.getDropdownMenu(data.element.closest('.inputAddon').id);
-                       var item, label = '';
-                       for (var i = 0, length = dropdownMenu.childElementCount; i < length; i++) {
-                               item = dropdownMenu.children[i];
-                               
-                               var itemLanguageId = elData(item, 'language-id');
-                               if (itemLanguageId.length && languageId === ~~itemLanguageId) {
-                                       label = item.children[0].textContent;
-                               }
-                       }
-                       
-                       // save current value
-                       if (data.languageId !== languageId) {
-                               var values = _values.get(elementId);
-                               
-                               if (data.languageId) {
-                                       values.set(data.languageId, data.element.value);
-                               }
-                               
-                               if (languageId === 0) {
-                                       _values.set(elementId, new Dictionary());
-                               }
-                               else if (data.buttonLabel.classList.contains('active') || isInit === true) {
-                                       data.element.value = (values.has(languageId)) ? values.get(languageId) : '';
-                               }
-                               
-                               // update label
-                               data.buttonLabel.textContent = label;
-                               data.buttonLabel.classList[(languageId ? 'add' : 'remove')]('active');
-                               
-                               data.languageId = languageId;
-                       }
-                       
-                       if (!isInit) {
-                               data.element.blur();
-                               data.element.focus();
-                       }
-                       
-                       if (data.callbacks.has('select')) {
-                               data.callbacks.get('select')(data.element);
-                       }
-               },
-               
-               /**
-                * Callback for dropdowns being opened, flags items with a missing value for one or more languages.
-                * 
-                * @param       {string}        containerId     dropdown container id
-                * @param       {string}        action          toggle action, can be `open` or `close`
-                */
-               _dropdownToggle: function(containerId, action) {
-                       if (action !== 'open') {
-                               return;
-                       }
-                       
-                       var dropdownMenu = UiSimpleDropdown.getDropdownMenu(containerId);
-                       var elementId = elData(elById(containerId), 'input-id');
-                       var data = _elements.get(elementId);
-                       var values = _values.get(elementId);
-                       
-                       var item, languageId;
-                       for (var i = 0, length = dropdownMenu.childElementCount; i < length; i++) {
-                               item = dropdownMenu.children[i];
-                               languageId = ~~elData(item, 'language-id');
-                               
-                               if (languageId) {
-                                       var hasMissingValue = false;
-                                       if (data.languageId) {
-                                               if (languageId === data.languageId) {
-                                                       hasMissingValue = (data.element.value.trim() === '');
-                                               }
-                                               else {
-                                                       hasMissingValue = (!values.get(languageId));
-                                               }
-                                       }
-                                       
-                                       item.classList[(hasMissingValue ? 'add' : 'remove')]('missingValue');
-                               }
-                       }
-               },
-               
-               /**
-                * Inserts hidden fields for i18n input on submit.
-                * 
-                * @param       {Object}        event           event object
-                */
-               _submit: function(event) {
-                       var elementIds = _forms.get(event.currentTarget);
-                       
-                       var data, elementId, input, values;
-                       for (var i = 0, length = elementIds.length; i < length; i++) {
-                               elementId = elementIds[i];
-                               data = _elements.get(elementId);
-                               if (data.isEnabled) {
-                                       values = _values.get(elementId);
-                                       
-                                       if (data.callbacks.has('submit')) {
-                                               data.callbacks.get('submit')(data.element);
-                                       }
-                                       
-                                       // update with current value
-                                       if (data.languageId) {
-                                               values.set(data.languageId, data.element.value);
-                                       }
-                                       
-                                       if (values.size) {
-                                               values.forEach(function(value, languageId) {
-                                                       input = elCreate('input');
-                                                       input.type = 'hidden';
-                                                       input.name = elementId + '_i18n[' + languageId + ']';
-                                                       input.value = value;
-                                                       
-                                                       event.currentTarget.appendChild(input);
-                                               });
-                                               
-                                               // remove name attribute to enforce i18n values
-                                               data.element.removeAttribute('name');
-                                       }
-                               }
-                       }
-               },
-               
-               /**
-                * Returns the values of an input field.
-                * 
-                * @param       {string}        elementId       input element id
-                * @return      {Dictionary}    values stored for the different languages
-                */
-               getValues: function(elementId) {
-                       var element = _elements.get(elementId);
-                       if (element === undefined) {
-                               throw new Error("Expected a valid i18n input element, '" + elementId + "' is not i18n input field.");
-                       }
-                       
-                       var values = _values.get(elementId);
-                       
-                       // update with current value
-                       values.set(element.languageId, element.element.value);
-                       
-                       return values;
-               },
-               
-               /**
-                * Sets the values of an input field.
-                * 
-                * @param       {string}        elementId       input element id
-                * @param       {Dictionary}    values          values for the different languages
-                */
-               setValues: function(elementId, values) {
-                       var element = _elements.get(elementId);
-                       if (element === undefined) {
-                               throw new Error("Expected a valid i18n input element, '" + elementId + "' is not i18n input field.");
-                       }
-                       
-                       if (Core.isPlainObject(values)) {
-                               values = Dictionary.fromObject(values);
-                       }
-                       
-                       element.element.value = '';
-                       
-                       if (values.has(0)) {
-                               element.element.value = values.get(0);
-                               values['delete'](0);
-                               _values.set(elementId, values);
-                               this._select(elementId, 0, true);
-                               return;
-                       }
-                       
-                       _values.set(elementId, values);
-                       
-                       element.languageId = 0;
-                       //noinspection JSUnresolvedVariable
-                       this._select(elementId, LANGUAGE_ID, true);
-               },
-               
-               /**
-                * Disables the i18n interface for an input field.
-                * 
-                * @param       {string}        elementId       input element id
-                */
-               disable: function(elementId) {
-                       var element = _elements.get(elementId);
-                       if (element === undefined) {
-                               throw new Error("Expected a valid element, '" + elementId + "' is not an i18n input field.");
-                       }
-                       
-                       if (!element.isEnabled) return;
-                       
-                       element.isEnabled = false;
-                       
-                       // hide language dropdown
-                       //noinspection JSCheckFunctionSignatures
-                       elHide(element.buttonLabel.parentNode);
-                       var dropdownContainer = element.buttonLabel.parentNode.parentNode;
-                       dropdownContainer.classList.remove('inputAddon');
-                       dropdownContainer.classList.remove('dropdown');
-               },
-               
-               /**
-                * Enables the i18n interface for an input field.
-                * 
-                * @param       {string}        elementId       input element id
-                */
-               enable: function(elementId) {
-                       var element = _elements.get(elementId);
-                       if (element === undefined) {
-                               throw new Error("Expected a valid i18n input element, '" + elementId + "' is not i18n input field.");
-                       }
-                       
-                       if (element.isEnabled) return;
-                       
-                       element.isEnabled = true;
-                       
-                       // show language dropdown
-                       //noinspection JSCheckFunctionSignatures
-                       elShow(element.buttonLabel.parentNode);
-                       var dropdownContainer = element.buttonLabel.parentNode.parentNode;
-                       dropdownContainer.classList.add('inputAddon');
-                       dropdownContainer.classList.add('dropdown');
-               },
-               
-               /**
-                * Returns true if i18n input is enabled for an input field.
-                * 
-                * @param       {string}        elementId       input element id
-                * @return      {boolean}
-                */
-               isEnabled: function(elementId) {
-                       var element = _elements.get(elementId);
-                       if (element === undefined) {
-                               throw new Error("Expected a valid i18n input element, '" + elementId + "' is not i18n input field.");
-                       }
-                       
-                       return element.isEnabled;
-               },
-               
-               /**
-                * Returns true if the value of an i18n input field is valid.
-                * 
-                * If the element is disabled, true is returned.
-                * 
-                * @param       {string}        elementId               input element id
-                * @param       {boolean}       permitEmptyValue        if true, input may be empty for all languages
-                * @return      {boolean}       true if input is valid
-                */
-               validate: function(elementId, permitEmptyValue) {
-                       var element = _elements.get(elementId);
-                       if (element === undefined) {
-                               throw new Error("Expected a valid i18n input element, '" + elementId + "' is not i18n input field.");
-                       }
-                       
-                       if (!element.isEnabled) return true;
-                       
-                       var values = _values.get(elementId);
-                       
-                       var dropdownMenu = UiSimpleDropdown.getDropdownMenu(element.element.parentNode.id);
-                       
-                       if (element.languageId) {
-                               values.set(element.languageId, element.element.value);
-                       }
-                       
-                       var item, languageId;
-                       var hasEmptyValue = false, hasNonEmptyValue = false;
-                       for (var i = 0, length = dropdownMenu.childElementCount; i < length; i++) {
-                               item = dropdownMenu.children[i];
-                               languageId = ~~elData(item, 'language-id');
-                               
-                               if (languageId) {
-                                       if (!values.has(languageId) || values.get(languageId).length === 0) {
-                                               // input has non-empty value for previously checked language
-                                               if (hasNonEmptyValue) {
-                                                       return false;
-                                               }
-                                               
-                                               hasEmptyValue = true;
-                                       }
-                                       else {
-                                               // input has empty value for previously checked language
-                                               if (hasEmptyValue) {
-                                                       return false;
-                                               }
-                                               
-                                               hasNonEmptyValue = true;
-                                       }
-                               }
-                       }
-                       
-                       return (!hasEmptyValue || permitEmptyValue);
-               }
-       };
-});
-
-/**
- * I18n interface for wysiwyg input fields.
- * 
- * @author      Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module      WoltLabSuite/Core/Language/Text
- */
-define('WoltLabSuite/Core/Language/Text',['Core', './Input'], function (Core, LanguageInput) {
-       "use strict";
-       
-       /**
-        * @exports     WoltLabSuite/Core/Language/Text
-        */
-       return {
-               /**
-                * Initializes an WYSIWYG input field.
-                * 
-                * @param       {string}        elementId               input element id
-                * @param       {Object}        values                  preset values per language id
-                * @param       {Object}        availableLanguages      language names per language id
-                * @param       {boolean}       forceSelection          require i18n input
-                */
-               init: function(elementId, values, availableLanguages, forceSelection) {
-                       var element = elById(elementId);
-                       if (!element || element.nodeName !== 'TEXTAREA' || !element.classList.contains('wysiwygTextarea')) {
-                               throw new Error("Expected <textarea class=\"wysiwygTextarea\" /> for id '" + elementId + "'.");
-                       }
-                       
-                       LanguageInput.init(elementId, values, availableLanguages, forceSelection);
-                       
-                       //noinspection JSUnresolvedFunction
-                       LanguageInput.registerCallback(elementId, 'select', this._callbackSelect.bind(this));
-                       //noinspection JSUnresolvedFunction
-                       LanguageInput.registerCallback(elementId, 'submit', this._callbackSubmit.bind(this));
-               },
-               
-               /**
-                * Refreshes the editor content on language switch.
-                * 
-                * @param       {Element}       element         input element
-                * @protected
-                */
-               _callbackSelect: function (element) {
-                       if (window.jQuery !== undefined) {
-                               window.jQuery(element).redactor('code.set', element.value);
-                       }
-               },
-               
-               /**
-                * Refreshes the input element value on submit.
-                * 
-                * @param       {Element}       element         input element
-                * @protected
-                */
-               _callbackSubmit: function (element) {
-                       if (window.jQuery !== undefined) {
-                               element.value = window.jQuery(element).redactor('code.get');
-                       }
-               }
-       }
-});
-
-/**
- * Handles editing media files via dialog.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Media/Editor
- */
-define(
-       'WoltLabSuite/Core/Media/Editor',[
-               'Ajax',                         'Core',                       'Dictionary',          'Dom/ChangeListener',
-               'Dom/Traverse',                 'Language',                   'Ui/Dialog',           'Ui/Notification',
-               'WoltLabSuite/Core/Language/Chooser', 'WoltLabSuite/Core/Language/Input', 'EventKey'
-       ],
-       function(
-               Ajax,                            Core,                         Dictionary,            DomChangeListener,
-               DomTraverse,                     Language,                     UiDialog,              UiNotification,
-               LanguageChooser,                 LanguageInput,                EventKey
-       )
-{
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       _ajaxSetup: function() {},
-                       _ajaxSuccess: function() {},
-                       _close: function() {},
-                       _keyPress: function() {},
-                       _saveData: function() {},
-                       _updateLanguageFields: function() {},
-                       edit: function() {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * @constructor
-        */
-       function MediaEditor(callbackObject) {
-               this._callbackObject = callbackObject || {};
-               
-               if (this._callbackObject._editorClose && typeof this._callbackObject._editorClose !== 'function') {
-                       throw new TypeError("Callback object has no function '_editorClose'.");
-               }
-               if (this._callbackObject._editorSuccess && typeof this._callbackObject._editorSuccess !== 'function') {
-                       throw new TypeError("Callback object has no function '_editorSuccess'.");
-               }
-               
-               this._media = null;
-               this._availableLanguageCount = 1;
-               this._categoryIds = [];
-               this._oldCategoryId = 0;
-               
-               this._dialogs = new Dictionary();
-       }
-       MediaEditor.prototype = {
-               /**
-                * Returns the data for Ajax to setup the Ajax/Request object.
-                * 
-                * @return      {object}        setup data for Ajax/Request object
-                */
-               _ajaxSetup: function() {
-                       return {
-                               data: {
-                                       actionName: 'update',
-                                       className: 'wcf\\data\\media\\MediaAction'
-                               }
-                       };
-               },
-               
-               /**
-                * Handles successful AJAX requests.
-                * 
-                * @param       {object}        data    response data
-                */
-               _ajaxSuccess: function(data) {
-                       UiNotification.show();
-                       
-                       if (this._callbackObject._editorSuccess) {
-                               this._callbackObject._editorSuccess(this._media, this._oldCategoryId);
-                               this._oldCategoryId = 0;
-                       }
-                       
-                       UiDialog.close('mediaEditor_' + this._media.mediaID);
-                       
-                       this._media = null;
-               },
-               
-               /**
-                * Is called if an editor is manually closed by the user.
-                */
-               _close: function() {
-                       this._media = null;
-                       
-                       if (this._callbackObject._editorClose) {
-                               this._callbackObject._editorClose();
-                       }
-               },
-               
-               /**
-                * Handles the `[ENTER]` key to submit the form.
-                * 
-                * @param       {object}        event           event object
-                */
-               _keyPress: function(event) {
-                       if (EventKey.Enter(event)) {
-                               event.preventDefault();
-                               
-                               this._saveData();
-                       }
-               },
-               
-               /**
-                * Saves the data of the currently edited media.
-                */
-               _saveData: function() {
-                       var content = UiDialog.getDialog('mediaEditor_' + this._media.mediaID).content;
-                       
-                       var categoryId = elBySel('select[name=categoryID]', content);
-                       var altText = elBySel('input[name=altText]', content);
-                       var caption = elBySel('textarea[name=caption]', content);
-                       var captionEnableHtml = elBySel('input[name=captionEnableHtml]', content);
-                       var title = elBySel('input[name=title]', content);
-                       
-                       var hasError = false;
-                       var altTextError = (altText ? DomTraverse.childByClass(altText.parentNode.parentNode, 'innerError') : false);
-                       var captionError = (caption ? DomTraverse.childByClass(caption.parentNode.parentNode, 'innerError') : false);
-                       var titleError = DomTraverse.childByClass(title.parentNode.parentNode, 'innerError');
-                       
-                       // category
-                       this._oldCategoryId = this._media.categoryID;
-                       if (this._categoryIds.length) {
-                               this._media.categoryID = ~~categoryId.value;
-                               
-                               // if the selected category id not valid (manipulated DOM), ignore
-                               if (this._categoryIds.indexOf(this._media.categoryID) === -1) {
-                                       this._media.categoryID = 0;
-                               }
-                       }
-                       
-                       // language and multilingualism
-                       if (this._availableLanguageCount > 1) {
-                               this._media.isMultilingual = ~~elBySel('input[name=isMultilingual]', content).checked;
-                               this._media.languageID = this._media.isMultilingual ? null : LanguageChooser.getLanguageId('mediaEditor_' + this._media.mediaID + '_languageID');
-                       }
-                       else {
-                               this._media.languageID = LANGUAGE_ID;
-                       }
-                       
-                       // altText, caption and title
-                       this._media.altText = {};
-                       this._media.caption = {};
-                       this._media.title = {};
-                       if (this._availableLanguageCount > 1 && this._media.isMultilingual) {
-                               if (elById('altText_' + this._media.mediaID) && !LanguageInput.validate('altText_' + this._media.mediaID, true)) {
-                                       hasError = true;
-                                       if (!altTextError) {
-                                               var error = elCreate('small');
-                                               error.className = 'innerError';
-                                               error.textContent = Language.get('wcf.global.form.error.multilingual');
-                                               altText.parentNode.parentNode.appendChild(error);
-                                       }
-                               }
-                               if (elById('caption_' + this._media.mediaID) && !LanguageInput.validate('caption_' + this._media.mediaID, true)) {
-                                       hasError = true;
-                                       if (!captionError) {
-                                               var error = elCreate('small');
-                                               error.className = 'innerError';
-                                               error.textContent = Language.get('wcf.global.form.error.multilingual');
-                                               caption.parentNode.parentNode.appendChild(error);
-                                       }
-                               }
-                               if (!LanguageInput.validate('title_' + this._media.mediaID, true)) {
-                                       hasError = true;
-                                       if (!titleError) {
-                                               var error = elCreate('small');
-                                               error.className = 'innerError';
-                                               error.textContent = Language.get('wcf.global.form.error.multilingual');
-                                               title.parentNode.parentNode.appendChild(error);
-                                       }
-                               }
-                               
-                               this._media.altText = (elById('altText_' + this._media.mediaID) ? LanguageInput.getValues('altText_' + this._media.mediaID).toObject() : '');
-                               this._media.caption = (elById('caption_' + this._media.mediaID) ? LanguageInput.getValues('caption_' + this._media.mediaID).toObject() : '');
-                               this._media.title = LanguageInput.getValues('title_' + this._media.mediaID).toObject();
-                       }
-                       else {
-                               this._media.altText[this._media.languageID] = (altText ? altText.value : '');
-                               this._media.caption[this._media.languageID] = (caption ? caption.value : '');
-                               this._media.title[this._media.languageID] = title.value;
-                       }
-                       
-                       // captionEnableHtml
-                       this._media.captionEnableHtml = ~~elBySel('input[name=captionEnableHtml]', content).checked;
-                       
-                       var aclValues = {
-                               allowAll: ~~elById('mediaEditor_' + this._media.mediaID + '_aclAllowAll').checked,
-                               group: [],
-                               user: []
-                       };
-                       
-                       var aclGroups = elBySelAll('input[name="aclValues[group][]"]', content);
-                       for (var i = 0, length = aclGroups.length; i < length; i++) {
-                               aclValues.group.push(~~aclGroups[i].value);
-                       }
-                       
-                       var aclUsers = elBySelAll('input[name="aclValues[user][]"]', content);
-                       for (var i = 0, length = aclUsers.length; i < length; i++) {
-                               aclValues.user.push(~~aclUsers[i].value);
-                       }
-                       
-                       if (!hasError) {
-                               if (altTextError) elRemove(altTextError);
-                               if (captionError) elRemove(captionError);
-                               if (titleError) elRemove(titleError);
-                               
-                               Ajax.api(this, {
-                                       actionName: 'update',
-                                       objectIDs: [ this._media.mediaID ],
-                                       parameters: {
-                                               aclValues: aclValues,
-                                               altText: this._media.altText,
-                                               caption: this._media.caption,
-                                               data: {
-                                                       captionEnableHtml: this._media.captionEnableHtml,
-                                                       categoryID: this._media.categoryID,
-                                                       isMultilingual: this._media.isMultilingual,
-                                                       languageID: this._media.languageID
-                                               },
-                                               title: this._media.title
-                                       }
-                               });
-                       }
-               },
-               
-               /**
-                * Updates language-related input fields depending on whether multilingualism
-                * is enabled.
-                */
-               _updateLanguageFields: function(event, element) {
-                       if (event) element = event.currentTarget;
-                       
-                       var languageChooserContainer = elById('mediaEditor_' + this._media.mediaID + '_languageIDContainer').parentNode;
-                       
-                       if (element.checked) {
-                               LanguageInput.enable('title_' + this._media.mediaID);
-                               if (elById('caption_' + this._media.mediaID)) LanguageInput.enable('caption_' + this._media.mediaID);
-                               if (elById('altText_' + this._media.mediaID)) LanguageInput.enable('altText_' + this._media.mediaID);
-                               
-                               elHide(languageChooserContainer);
-                       }
-                       else {
-                               LanguageInput.disable('title_' + this._media.mediaID);
-                               if (elById('caption_' + this._media.mediaID)) LanguageInput.disable('caption_' + this._media.mediaID);
-                               if (elById('altText_' + this._media.mediaID)) LanguageInput.disable('altText_' + this._media.mediaID);
-                               
-                               elShow(languageChooserContainer);
-                       }
-               },
-               
-               /**
-                * Edits the media with the given data.
-                * 
-                * @param       {object|integer}        media           data of the edited media or media id for which the data will be loaded
-                */
-               edit: function(media) {
-                       if (typeof media !== 'object') {
-                               media = {
-                                       mediaID: ~~media
-                               };
-                       }
-                       
-                       if (this._media !== null) {
-                               throw new Error("Cannot edit media with id '" + media.mediaID + "' while editing media with id '" + this._media.mediaID + "'");
-                       }
-                       
-                       this._media = media;
-                       
-                       if (!this._dialogs.has('mediaEditor_' + media.mediaID)) {
-                               this._dialogs.set('mediaEditor_' + media.mediaID, {
-                                       _dialogSetup: function() {
-                                               return {
-                                                       id: 'mediaEditor_' + media.mediaID,
-                                                       options: {
-                                                               backdropCloseOnClick: false,
-                                                               onClose: this._close.bind(this),
-                                                               title: Language.get('wcf.media.edit')
-                                                       },
-                                                       source: {
-                                                               after: (function(content, data) {
-                                                                       this._availableLanguageCount = ~~data.returnValues.availableLanguageCount;
-                                                                       this._categoryIds = data.returnValues.categoryIDs.map(function(number) {
-                                                                               return ~~number;
-                                                                       });
-                                                                       
-                                                                       var didLoadMediaData = false;
-                                                                       if (data.returnValues.mediaData) {
-                                                                               this._media = data.returnValues.mediaData;
-                                                                               
-                                                                               didLoadMediaData = true;
-                                                                       }
-                                                                       
-                                                                       // make sure that the language chooser is initialized first
-                                                                       setTimeout(function() {
-                                                                               if (this._availableLanguageCount > 1) {
-                                                                                       LanguageChooser.setLanguageId('mediaEditor_' + this._media.mediaID + '_languageID', this._media.languageID || LANGUAGE_ID);
-                                                                               }
-                                                                               
-                                                                               if (this._categoryIds.length) {
-                                                                                       elBySel('select[name=categoryID]', content).value = ~~this._media.categoryID;
-                                                                               }
-                                                                               
-                                                                               var title = elBySel('input[name=title]', content);
-                                                                               var altText = elBySel('input[name=altText]', content);
-                                                                               var caption = elBySel('textarea[name=caption]', content);
-                                                                               
-                                                                               if (this._availableLanguageCount > 1 && this._media.isMultilingual) {
-                                                                                       if (elById('altText_' + this._media.mediaID)) LanguageInput.setValues('altText_' + this._media.mediaID, Dictionary.fromObject(this._media.altText || { }));
-                                                                                       if (elById('caption_' + this._media.mediaID)) LanguageInput.setValues('caption_' + this._media.mediaID, Dictionary.fromObject(this._media.caption || { }));
-                                                                                       LanguageInput.setValues('title_' + this._media.mediaID, Dictionary.fromObject(this._media.title || { }));
-                                                                               }
-                                                                               else {
-                                                                                       title.value = this._media.title ? this._media.title[this._media.languageID || LANGUAGE_ID] : ''; 
-                                                                                       if (altText) altText.value = this._media.altText ? this._media.altText[this._media.languageID || LANGUAGE_ID] : '';
-                                                                                       if (caption) caption.value = this._media.caption ? this._media.caption[this._media.languageID || LANGUAGE_ID] : '';
-                                                                               }
-                                                                               
-                                                                               if (this._availableLanguageCount > 1) {
-                                                                                       var isMultilingual = elBySel('input[name=isMultilingual]', content);
-                                                                                       isMultilingual.addEventListener('change', this._updateLanguageFields.bind(this));
-                                                                                       
-                                                                                       this._updateLanguageFields(null, isMultilingual);
-                                                                               }
-                                                                               
-                                                                               var keyPress = this._keyPress.bind(this);
-                                                                               if (altText) altText.addEventListener('keypress', keyPress);
-                                                                               title.addEventListener('keypress', keyPress);
-                                                                               
-                                                                               elBySel('button[data-type=submit]', content).addEventListener(WCF_CLICK_EVENT, this._saveData.bind(this));
-                                                                               
-                                                                               // remove focus from input elements and scroll dialog to top
-                                                                               document.activeElement.blur();
-                                                                               elById('mediaEditor_' + this._media.mediaID).parentNode.scrollTop = 0;
-                                                                               
-                                                                               DomChangeListener.trigger();
-                                                                       }.bind(this), 200);
-                                                               }).bind(this),
-                                                               data: {
-                                                                       actionName: 'getEditorDialog',
-                                                                       className: 'wcf\\data\\media\\MediaAction',
-                                                                       objectIDs: [media.mediaID]
-                                                               }
-                                                       }
-                                               };
-                                       }.bind(this)
-                               });
-                       }
-                       
-                       UiDialog.open(this._dialogs.get('mediaEditor_' + media.mediaID));
-               }
-       };
-       
-       return MediaEditor;
-});
-
-/**
- * Uploads media files.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Media/Upload
- */
-define(
-       'WoltLabSuite/Core/Media/Upload',[
-               'Core',
-               'DateUtil',
-               'Dom/ChangeListener',
-               'Dom/Traverse',
-               'Dom/Util',
-               'EventHandler',
-               'Language',
-               'Permission',
-               'Upload',
-               'User',
-               'WoltLabSuite/Core/FileUtil'
-       ],
-       function(
-               Core,
-               DateUtil,
-               DomChangeListener,
-               DomTraverse,
-               DomUtil,
-               EventHandler,
-               Language,
-               Permission,
-               Upload,
-               User,
-               FileUtil
-       )
-{
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       _createFileElement: function() {},
-                       _getParameters: function() {},
-                       _success: function() {},
-                       _uploadFiles: function() {},
-                       _createButton: function() {},
-                       _createFileElements: function() {},
-                       _failure: function() {},
-                       _insertButton: function() {},
-                       _progress: function() {},
-                       _removeButton: function() {},
-                       _upload: function() {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * @constructor
-        */
-       function MediaUpload(buttonContainerId, targetId, options) {
-               options = options || {};
-               
-               this._mediaManager = null;
-               if (options.mediaManager) {
-                       this._mediaManager = options.mediaManager;
-                       delete options.mediaManager;
-               }
-               this._categoryId = null;
-               
-               Upload.call(this, buttonContainerId, targetId, Core.extend({
-                       className: 'wcf\\data\\media\\MediaAction',
-                       multiple: this._mediaManager ? true : false,
-                       singleFileRequests: true
-               }, options));
-       }
-       Core.inherit(MediaUpload, Upload, {
-               /**
-                * @see WoltLabSuite/Core/Upload#_createFileElement
-                */
-               _createFileElement: function(file) {
-                       var fileElement;
-                       if (this._target.nodeName === 'OL' || this._target.nodeName === 'UL') {
-                               fileElement = elCreate('li');
-                       }
-                       else if (this._target.nodeName === 'TBODY') {
-                               var firstTr = elByTag('TR', this._target)[0];
-                               var tableContainer = this._target.parentNode.parentNode;
-                               if (tableContainer.style.getPropertyValue('display') === 'none') {
-                                       fileElement = firstTr;
-                                       
-                                       tableContainer.style.removeProperty('display');
-                                       
-                                       elRemove(elById(elData(this._target, 'no-items-info')));
-                               }
-                               else {
-                                       fileElement = firstTr.cloneNode(true);
-                                       
-                                       // regenerate id of table row
-                                       fileElement.removeAttribute('id');
-                                       DomUtil.identify(fileElement);
-                               }
-                               
-                               var cells = elByTag('TD', fileElement), cell;
-                               for (var i = 0, length = cells.length; i < length; i++) {
-                                       cell = cells[i];
-                                       
-                                       if (cell.classList.contains('columnMark')) {
-                                               elBySelAll('[data-object-id]', cell, elHide);
-                                       }
-                                       else if (cell.classList.contains('columnIcon')) {
-                                               elBySelAll('[data-object-id]', cell, elHide);
-                                               
-                                               elByClass('mediaEditButton', cell)[0].classList.add('jsMediaEditButton');
-                                               elData(elByClass('jsDeleteButton', cell)[0], 'confirm-message-html', Language.get('wcf.media.delete.confirmMessage', {
-                                                       title: file.name
-                                               }));
-                                       }
-                                       else if (cell.classList.contains('columnFilename')) {
-                                               // replace copied image with spinner
-                                               var image = elByTag('IMG', cell);
-                                               
-                                               if (!image.length) {
-                                                       image = elByClass('icon48', cell);
-                                               }
-                                               
-                                               var spinner = elCreate('span');
-                                               spinner.className = 'icon icon48 fa-spinner mediaThumbnail';
-                                               
-                                               DomUtil.replaceElement(image[0], spinner);
-                                               
-                                               // replace title and uploading user
-                                               var ps = elBySelAll('.box48 > div > p', cell);
-                                               ps[0].textContent = file.name;
-                                               
-                                               var userLink = elByTag('A', ps[1])[0];
-                                               if (!userLink) {
-                                                       userLink = elCreate('a');
-                                                       elByTag('SMALL', ps[1])[0].appendChild(userLink);
-                                               }
-                                               
-                                               userLink.setAttribute('href', User.getLink());
-                                               userLink.textContent = User.username;
-                                       }
-                                       else if (cell.classList.contains('columnUploadTime')) {
-                                               cell.innerHTML = '';
-                                               cell.appendChild(DateUtil.getTimeElement(new Date()));
-                                       }
-                                       else if (cell.classList.contains('columnDigits')) {
-                                               cell.textContent = FileUtil.formatFilesize(file.size);
-                                       }
-                                       else {
-                                               // empty the other cells
-                                               cell.innerHTML = '';
-                                       }
-                               }
-                               
-                               DomUtil.prepend(fileElement, this._target);
-                               
-                               return fileElement;
-                       }
-                       else {
-                               fileElement = elCreate('p');
-                       }
-                       
-                       var thumbnail = elCreate('div');
-                       thumbnail.className = 'mediaThumbnail';
-                       fileElement.appendChild(thumbnail);
-                       
-                       var fileIcon = elCreate('span');
-                       fileIcon.className = 'icon icon144 fa-spinner';
-                       thumbnail.appendChild(fileIcon);
-                       
-                       var mediaInformation = elCreate('div');
-                       mediaInformation.className = 'mediaInformation';
-                       fileElement.appendChild(mediaInformation);
-                       
-                       var p = elCreate('p');
-                       p.className = 'mediaTitle';
-                       p.textContent = file.name;
-                       mediaInformation.appendChild(p);
-                       
-                       var progress = elCreate('progress');
-                       elAttr(progress, 'max', 100);
-                       mediaInformation.appendChild(progress);
-                       
-                       DomUtil.prepend(fileElement, this._target);
-                       
-                       DomChangeListener.trigger();
-                       
-                       return fileElement;
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Upload#_getParameters
-                */
-               _getParameters: function() {
-                       if (this._mediaManager) {
-                               var parameters = {
-                                       imagesOnly: this._mediaManager.getOption('imagesOnly')
-                               };
-                               
-                               var categoryId = this._mediaManager.getCategoryId();
-                               if (categoryId) {
-                                       parameters.categoryID = categoryId;
-                               }
-                               
-                               return Core.extend(MediaUpload._super.prototype._getParameters.call(this), parameters);
-                       }
-                       
-                       return MediaUpload._super.prototype._getParameters.call(this);
-               },
-               
-               /**
-                * Replaces the default or copied file icon with the actual file icon.
-                * 
-                * @param       {HTMLElement}   fileIcon        file icon element
-                * @param       {object}        media           media data
-                * @param       {integer}       size            size of the file icon in pixels
-                */
-               _replaceFileIcon: function(fileIcon, media, size) {
-                       if (media.tinyThumbnailType) {
-                               var img = elCreate('img');
-                               elAttr(img, 'src', media.tinyThumbnailLink);
-                               elAttr(img, 'alt', '');
-                               img.style.setProperty('width', size + 'px');
-                               img.style.setProperty('height', size + 'px');
-                               
-                               DomUtil.replaceElement(fileIcon, img);
-                       }
-                       else {
-                               fileIcon.classList.remove('fa-spinner');
-                               
-                               var fileIconName = FileUtil.getIconNameByFilename(media.filename);
-                               if (fileIconName) {
-                                       fileIconName = '-' + fileIconName;
-                               }
-                               fileIcon.classList.add('fa-file' + fileIconName + '-o');
-                       }
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Upload#_success
-                */
-               _success: function(uploadId, data) {
-                       var files = this._fileElements[uploadId];
-                       
-                       for (var i = 0, length = files.length; i < length; i++) {
-                               var file = files[i];
-                               var internalFileId = elData(file, 'internal-file-id');
-                               var media = data.returnValues.media[internalFileId];
-                               
-                               if (file.tagName === 'TR') {
-                                       if (media) {
-                                               // update object id
-                                               var objectIdElements = elBySelAll('[data-object-id]', file);
-                                               for (var i = 0, length = objectIdElements.length; i < length; i++) {
-                                                       elData(objectIdElements[i], 'object-id', ~~media.mediaID);
-                                                       elShow(objectIdElements[i]);
-                                               }
-                                               
-                                               elByClass('columnMediaID', file)[0].textContent = media.mediaID;
-                                               
-                                               // update icon
-                                               var fileIcon = elByClass('fa-spinner', file)[0];
-                                               this._replaceFileIcon(fileIcon, media, 48);
-                                       }
-                                       else {
-                                               var error = data.returnValues.errors[internalFileId];
-                                               if (!error) {
-                                                       error = {
-                                                               errorType: 'uploadFailed',
-                                                               filename: elData(file, 'filename')
-                                                       };
-                                               }
-                                               
-                                               var fileIcon = elByClass('fa-spinner', file)[0];
-                                               fileIcon.classList.remove('fa-spinner');
-                                               fileIcon.classList.add('fa-remove');
-                                               fileIcon.classList.add('pointer');
-                                               fileIcon.classList.add('jsTooltip');
-                                               elAttr(fileIcon, 'title', Language.get('wcf.global.button.delete'));
-                                               fileIcon.addEventListener(WCF_CLICK_EVENT, function (event) {
-                                                       elRemove(event.currentTarget.parentNode.parentNode.parentNode);
-                                                       
-                                                       EventHandler.fire('com.woltlab.wcf.media.upload', 'removedErroneousUploadRow');
-                                               });
-                                               
-                                               file.classList.add('uploadFailed');
-                                               
-                                               var p = elBySelAll('.columnFilename .box48 > div > p', file)[1];
-                                               
-                                               elInnerError(p, Language.get('wcf.media.upload.error.' + error.errorType, {
-                                                       filename: error.filename
-                                               }));
-                                               
-                                               elRemove(p);
-                                       }
-                               }
-                               else {
-                                       elRemove(DomTraverse.childByTag(DomTraverse.childByClass(file, 'mediaInformation'), 'PROGRESS'));
-                                       
-                                       if (media) {
-                                               var fileIcon = DomTraverse.childByTag(DomTraverse.childByClass(file, 'mediaThumbnail'), 'SPAN');
-                                               this._replaceFileIcon(fileIcon, media, 144);
-                                               
-                                               file.className = 'jsClipboardObject mediaFile';
-                                               elData(file, 'object-id', media.mediaID);
-                                               
-                                               if (this._mediaManager) {
-                                                       this._mediaManager.setupMediaElement(media, file);
-                                                       this._mediaManager.addMedia(media, file);
-                                               }
-                                       }
-                                       else {
-                                               var error = data.returnValues.errors[internalFileId];
-                                               if (!error) {
-                                                       error = {
-                                                               errorType: 'uploadFailed',
-                                                               filename: elData(file, 'filename')
-                                                       };
-                                               }
-                                               
-                                               var fileIcon = DomTraverse.childByTag(DomTraverse.childByClass(file, 'mediaThumbnail'), 'SPAN');
-                                               fileIcon.classList.remove('fa-spinner');
-                                               fileIcon.classList.add('fa-remove');
-                                               fileIcon.classList.add('pointer');
-                                               
-                                               file.classList.add('uploadFailed');
-                                               file.classList.add('jsTooltip');
-                                               elAttr(file, 'title', Language.get('wcf.global.button.delete'));
-                                               file.addEventListener(WCF_CLICK_EVENT, function () {
-                                                       elRemove(this);
-                                               });
-                                               
-                                               var title = DomTraverse.childByClass(DomTraverse.childByClass(file, 'mediaInformation'), 'mediaTitle');
-                                               title.innerText = Language.get('wcf.media.upload.error.' + error.errorType, {
-                                                       filename: error.filename
-                                               });
-                                       }
-                               }
-                               
-                               DomChangeListener.trigger();
-                       }
-                       
-                       EventHandler.fire('com.woltlab.wcf.media.upload', 'success', {
-                               files: files,
-                               isMultiFileUpload: this._multiFileUploadIds.indexOf(uploadId) !== -1,
-                               media: data.returnValues.media,
-                               upload: this,
-                               uploadId: uploadId
-                       });
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Upload#_uploadFiles
-                */
-               _uploadFiles: function(files, blob) {
-                       return MediaUpload._super.prototype._uploadFiles.call(this, files, blob);
-               }
-       });
-       
-       return MediaUpload;
-});
-
-/**
- * Uploads media files.
- *
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Media/List/Upload
- */
-define(
-       'WoltLabSuite/Core/Media/List/Upload',[
-               'Core', 'Dom/Util', '../Upload'
-       ],
-       function(
-               Core, DomUtil, MediaUpload
-       )
-{
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       _createButton: function() {},
-                       _success: function() {},
-                       _upload: function() {},
-                       _createFileElement: function() {},
-                       _getParameters: function() {},
-                       _uploadFiles: function() {},
-                       _createFileElements: function() {},
-                       _failure: function() {},
-                       _insertButton: function() {},
-                       _progress: function() {},
-                       _removeButton: function() {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * @constructor
-        */
-       function MediaListUpload(buttonContainerId, targetId, options) {
-               MediaUpload.call(this, buttonContainerId, targetId, options);
-       }
-       Core.inherit(MediaListUpload, MediaUpload, {
-               /**
-                * Creates the upload button.
-                */
-               _createButton: function() {
-                       MediaListUpload._super.prototype._createButton.call(this);
-                       
-                       var span = elBySel('span', this._button);
-                       
-                       var space = document.createTextNode(' ');
-                       DomUtil.prepend(space, span);
-                       
-                       var icon = elCreate('span');
-                       icon.className = 'icon icon16 fa-upload';
-                       DomUtil.prepend(icon, span);
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Upload#_getParameters
-                */
-               _getParameters: function() {
-                       if (this._options.categoryId) {
-                               return Core.extend(MediaListUpload._super.prototype._getParameters.call(this), {
-                                       categoryID: this._options.categoryId
-                               });
-                       }
-                       
-                       return MediaListUpload._super.prototype._getParameters.call(this);
-               }
-       });
-       
-       return MediaListUpload;
-});
-
-/**
- * Initializes modules required for media clipboard.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Media/Clipboard
- */
-define('WoltLabSuite/Core/Media/Clipboard',[
-               'Ajax',
-               'Dom/ChangeListener',
-               'EventHandler',
-               'Language',
-               'Ui/Dialog',
-               'Ui/Notification',
-               'WoltLabSuite/Core/Controller/Clipboard',
-               'WoltLabSuite/Core/Media/Editor',
-               'WoltLabSuite/Core/Media/List/Upload'
-       ],
-       function(
-               Ajax,
-               DomChangeListener,
-               EventHandler,
-               Language,
-               UiDialog,
-               UiNotification,
-               Clipboard,
-               MediaEditor,
-               MediaListUpload
-       ) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _ajaxSetup: function() {},
-                       _ajaxSuccess: function() {},
-                       _clipboardAction: function() {},
-                       _dialogSetup: function() {},
-                       _edit: function() {},
-                       _setCategory: function() {}
-               };
-               return Fake;
-       }
-       
-       var _clipboardObjectIds = [];
-       var _mediaManager;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Media/Clipboard
-        */
-       return {
-               init: function(pageClassName, hasMarkedItems, mediaManager) {
-                       Clipboard.setup({
-                               hasMarkedItems: hasMarkedItems,
-                               pageClassName: pageClassName
-                       });
-                       
-                       _mediaManager = mediaManager;
-                       
-                       EventHandler.add('com.woltlab.wcf.clipboard', 'com.woltlab.wcf.media', this._clipboardAction.bind(this));
-               },
-               
-               /**
-                * Returns the data used to setup the AJAX request object.
-                *
-                * @return      {object}        setup data
-                */
-               _ajaxSetup: function() {
-                       return {
-                               data: {
-                                       className: 'wcf\\data\\media\\MediaAction'
-                               }
-                       }
-               },
-               
-               /**
-                * Handles successful AJAX request.
-                *
-                * @param       {object}        data    response data
-                */
-               _ajaxSuccess: function(data) {
-                       switch (data.actionName) {
-                               case 'getSetCategoryDialog':
-                                       UiDialog.open(this, data.returnValues.template);
-                                       
-                                       break;
-                                       
-                               case 'setCategory':
-                                       UiDialog.close(this);
-                                       
-                                       UiNotification.show();
-                                       
-                                       Clipboard.reload();
-                                       
-                                       break;
-                       }
-               },
-               
-               /**
-                * Returns the data used to setup the dialog.
-                * 
-                * @return      {object}        setup data
-                */
-               _dialogSetup: function() {
-                       return {
-                               id: 'mediaSetCategoryDialog',
-                               options: {
-                                       onSetup: function(content) {
-                                               elBySel('button', content).addEventListener(WCF_CLICK_EVENT, function(event) {
-                                                       event.preventDefault();
-                                                       
-                                                       this._setCategory(~~elBySel('select[name="categoryID"]', content).value);
-                                                       
-                                                       event.currentTarget.disabled = true;
-                                               }.bind(this));
-                                       }.bind(this),
-                                       title: Language.get('wcf.media.setCategory')
-                               },
-                               source: null
-                       }
-               },
-               
-               /**
-                * Handles successful clipboard actions.
-                * 
-                * @param       {object}        actionData
-                */
-               _clipboardAction: function(actionData) {
-                       var mediaIds = actionData.data.parameters.objectIDs;
-                       
-                       switch (actionData.data.actionName) {
-                               case 'com.woltlab.wcf.media.delete':
-                                       // only consider events if the action has been executed
-                                       if (actionData.responseData !== null) {
-                                               _mediaManager.clipboardDeleteMedia(mediaIds);
-                                       }
-                                       
-                                       break;
-                                       
-                               case 'com.woltlab.wcf.media.insert':
-                                       _mediaManager.clipboardInsertMedia(mediaIds);
-                                       
-                                       break;
-                                       
-                               case 'com.woltlab.wcf.media.setCategory':
-                                       _clipboardObjectIds = mediaIds;
-                                       
-                                       Ajax.api(this, {
-                                               actionName: 'getSetCategoryDialog'
-                                       });
-                                       
-                                       break;
-                       }
-               },
-               
-               /**
-                * Sets the category of the marked media files.
-                * 
-                * @param       {int}           categoryID      selected category id
-                */
-               _setCategory: function(categoryID) {
-                       Ajax.api(this, {
-                               actionName: 'setCategory',
-                               objectIDs: _clipboardObjectIds,
-                               parameters: {
-                                       categoryID: categoryID
-                               }
-                       });
-               }
-       }
-});
-/**
- * Provides desktop notifications via periodic polling with an
- * increasing request delay on inactivity.
- * 
- * @author      Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module      WoltLabSuite/Core/Notification/Handler
- */
-define('WoltLabSuite/Core/Notification/Handler',['Ajax', 'Core', 'EventHandler', 'StringUtil'], function(Ajax, Core, EventHandler, StringUtil) {
-       "use strict";
-       
-       if (!('Promise' in window) || !('Notification' in window)) {
-               // fake object exposed to ancient browsers (*cough* IE11 *cough*)
-               return {
-                       setup: function () {}
-               }
-       }
-       
-       var _allowNotification = false;
-       var _icon = '';
-       var _inactiveSince = 0;
-       //noinspection JSUnresolvedVariable
-       var _lastRequestTimestamp = window.TIME_NOW;
-       var _requestTimer = null;
-       var _sessionKeepAlive = 0;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Notification/Handler
-        */
-       return {
-               /**
-                * Initializes the desktop notification system.
-                * 
-                * @param       {Object}        options         initialization options
-                */
-               setup: function (options) {
-                       options = Core.extend({
-                               enableNotifications: false,
-                               icon: '',
-                               sessionKeepAlive: 0
-                       }, options);
-                       
-                       _icon = options.icon;
-                       _sessionKeepAlive = options.sessionKeepAlive * 60;
-                       
-                       this._prepareNextRequest();
-                       
-                       document.addEventListener('visibilitychange', this._onVisibilityChange.bind(this));
-                       window.addEventListener('storage', this._onStorage.bind(this));
-                       
-                       this._onVisibilityChange(null);
-                       
-                       if (options.enableNotifications) {
-                               switch (window.Notification.permission) {
-                                       case 'granted':
-                                               _allowNotification = true;
-                                               break;
-                                       case 'default':
-                                               window.Notification.requestPermission(function (result) {
-                                                       if (result === 'granted') {
-                                                               _allowNotification = true;
-                                                       }
-                                               });
-                                               break;
-                               }
-                       }
-               },
-               
-               /**
-                * Detects when this window is hidden or restored.
-                * 
-                * @param       {Event}         event
-                * @protected
-                */
-               _onVisibilityChange: function(event) {
-                       // document was hidden before
-                       if (event !== null && !document.hidden) {
-                               var difference = (Date.now() - _inactiveSince) / 60000;
-                               if (difference > 4) {
-                                       this._resetTimer();
-                                       this._dispatchRequest();
-                               }
-                       }
-                       
-                       _inactiveSince = (document.hidden) ? Date.now() : 0;
-               },
-               
-               /**
-                * Returns the delay in minutes before the next request should be dispatched.
-                * 
-                * @return      {int}
-                * @protected
-                */
-               _getNextDelay: function() {
-                       if (_inactiveSince === 0) return 5;
-                       
-                       // milliseconds -> minutes
-                       var inactiveMinutes = ~~((Date.now() - _inactiveSince) / 60000);
-                       if (inactiveMinutes < 15) {
-                               return 5;
-                       }
-                       else if (inactiveMinutes < 30) {
-                               return 10;
-                       }
-                       
-                       return 15;
-               },
-               
-               /**
-                * Resets the request delay timer.
-                * 
-                * @protected
-                */
-               _resetTimer: function() {
-                       if (_requestTimer !== null) {
-                               window.clearTimeout(_requestTimer);
-                               _requestTimer = null;
-                       }
-               },
-               
-               /**
-                * Schedules the next request using a calculated delay.
-                * 
-                * @protected
-                */
-               _prepareNextRequest: function() {
-                       this._resetTimer();
-                       
-                       var delay = Math.min(this._getNextDelay(), _sessionKeepAlive);
-                       _requestTimer = window.setTimeout(this._dispatchRequest.bind(this), delay * 60000);
-               },
-               
-               /**
-                * Requests new data from the server.
-                * 
-                * @protected
-                */
-               _dispatchRequest: function() {
-                       var parameters = {};
-                       EventHandler.fire('com.woltlab.wcf.notification', 'beforePoll', parameters);
-                       
-                       // this timestamp is used to determine new notifications and to avoid
-                       // notifications being displayed multiple times due to different origins
-                       // (=subdomains) used, because we cannot synchronize them in the client
-                       parameters.lastRequestTimestamp = _lastRequestTimestamp;
-                       
-                       Ajax.api(this, {
-                               parameters: parameters
-                       });
-               },
-               
-               /**
-                * Notifies subscribers for updated data received by another tab.
-                * 
-                * @protected
-                */
-               _onStorage: function() {
-                       // abort and re-schedule periodic request
-                       this._prepareNextRequest();
-                       
-                       var pollData, keepAliveData, abort = false;
-                       try {
-                               pollData = window.localStorage.getItem(Core.getStoragePrefix() + 'notification');
-                               keepAliveData = window.localStorage.getItem(Core.getStoragePrefix() + 'keepAliveData');
-                               
-                               pollData = JSON.parse(pollData);
-                               keepAliveData = JSON.parse(keepAliveData);
-                       }
-                       catch (e) {
-                               abort = true;
-                       }
-                       
-                       if (!abort) {
-                               EventHandler.fire('com.woltlab.wcf.notification', 'onStorage', {
-                                       pollData: pollData,
-                                       keepAliveData: keepAliveData
-                               });
-                       }
-               },
-               
-               _ajaxSuccess: function(data) {
-                       var abort = false;
-                       var keepAliveData = data.returnValues.keepAliveData;
-                       var pollData = data.returnValues.pollData;
-                       
-                       // forward keep alive data
-                       window.WCF.System.PushNotification.executeCallbacks(keepAliveData);
-                       
-                       // store response data in local storage
-                       try {
-                               window.localStorage.setItem(Core.getStoragePrefix() + 'notification', JSON.stringify(pollData));
-                               window.localStorage.setItem(Core.getStoragePrefix() + 'keepAliveData', JSON.stringify(keepAliveData));
-                       }
-                       catch (e) {
-                               // storage is unavailable, e.g. in private mode, log error and disable polling
-                               abort = true;
-                               
-                               window.console.log(e);
-                       }
-                       
-                       if (!abort) {
-                               this._prepareNextRequest();
-                       }
-                       
-                       _lastRequestTimestamp = data.returnValues.lastRequestTimestamp;
-                       
-                       EventHandler.fire('com.woltlab.wcf.notification', 'afterPoll', pollData);
-                       
-                       this._showNotification(pollData);
-               },
-               
-               /**
-                * Displays a desktop notification.
-                * 
-                * @param       {Object}        pollData
-                * @protected
-                */
-               _showNotification: function(pollData) {
-                       if (!_allowNotification) {
-                               return;
-                       }
-                       
-                       //noinspection JSUnresolvedVariable
-                       if (typeof pollData.notification === 'object' && typeof pollData.notification.message ===  'string') {
-                               //noinspection JSUnresolvedVariable
-                               var notification = new window.Notification(pollData.notification.title, {
-                                       body: StringUtil.unescapeHTML(pollData.notification.message),
-                                       icon: _icon
-                               });
-                               notification.onclick = function () {
-                                       window.focus();
-                                       notification.close();
-                                       
-                                       //noinspection JSUnresolvedVariable
-                                       window.location = pollData.notification.link;
-                               };
-                       }
-               },
-               
-               _ajaxSetup: function() {
-                       //noinspection JSUnresolvedVariable
-                       return {
-                               data: {
-                                       actionName: 'poll',
-                                       className: 'wcf\\data\\session\\SessionAction'
-                               },
-                               ignoreError: !window.ENABLE_DEBUG_MODE,
-                               silent: !window.ENABLE_DEBUG_MODE
-                       };
-               }
-       }
-});
-
-/**
- * Drag and Drop file uploads.
- * 
- * @author      Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module      WoltLabSuite/Core/Ui/Redactor/DragAndDrop
- */
-define('WoltLabSuite/Core/Ui/Redactor/DragAndDrop',['Dictionary', 'EventHandler', 'Language'], function (Dictionary, EventHandler, Language) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _dragOver: function() {},
-                       _drop: function() {},
-                       _dragLeave: function() {},
-                       _setup: function() {}
-               };
-               return Fake;
-       }
-       
-       var _didInit = false;
-       var _dragArea = new Dictionary();
-       var _isDragging = false;
-       var _isFile = false;
-       var _timerLeave = null;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Redactor/DragAndDrop
-        */
-       return {
-               /**
-                * Initializes drag and drop support for provided editor instance.
-                * 
-                * @param       {$.Redactor}    editor          editor instance
-                */
-               init: function (editor) {
-                       if (!_didInit) {
-                               this._setup();
-                       }
-                       
-                       _dragArea.set(editor.uuid, {
-                               editor: editor,
-                               element: null
-                       });
-               },
-               
-               /**
-                * Handles items dragged into the browser window.
-                * 
-                * @param       {Event}         event           drag event
-                */
-               _dragOver: function (event) {
-                       event.preventDefault();
-                       
-                       //noinspection JSUnresolvedVariable
-                       if (!event.dataTransfer || !event.dataTransfer.types) {
-                               return;
-                       }
-                       
-                       var isFirefox = false;
-                       //noinspection JSUnresolvedVariable
-                       for (var property in event.dataTransfer) {
-                               //noinspection JSUnresolvedVariable
-                               if (event.dataTransfer.hasOwnProperty(property) && property.match(/^moz/)) {
-                                       isFirefox = true;
-                                       break;
-                               }
-                       }
-                       
-                       // IE and WebKit set 'Files', Firefox sets 'application/x-moz-file' for files being dragged
-                       // and Safari just provides 'Files' along with a huge list of garbage
-                       _isFile = false;
-                       if (isFirefox) {
-                               // Firefox sets the 'Files' type even if the user is just dragging an on-page element
-                               //noinspection JSUnresolvedVariable
-                               if (event.dataTransfer.types[0] === 'application/x-moz-file') {
-                                       _isFile = true;
-                               }
-                       }
-                       else {
-                               //noinspection JSUnresolvedVariable
-                               for (var i = 0; i < event.dataTransfer.types.length; i++) {
-                                       //noinspection JSUnresolvedVariable
-                                       if (event.dataTransfer.types[i] === 'Files') {
-                                               _isFile = true;
-                                               break;
-                                       }
-                               }
-                       }
-                       
-                       if (!_isFile) {
-                               // user is just dragging around some garbage, ignore it
-                               return;
-                       }
-                       
-                       if (_isDragging) {
-                               // user is still dragging the file around
-                               return;
-                       }
-                       
-                       _isDragging = true;
-                       
-                       _dragArea.forEach((function (data, uuid) {
-                               var editor = data.editor.$editor[0];
-                               if (!editor.parentNode) {
-                                       _dragArea.delete(uuid);
-                                       return;
-                               }
-                               
-                               var element = data.element;
-                               if (element === null) {
-                                       element = elCreate('div');
-                                       element.className = 'redactorDropArea';
-                                       elData(element, 'element-id', data.editor.$element[0].id);
-                                       elData(element, 'drop-here', Language.get('wcf.attachment.dragAndDrop.dropHere'));
-                                       elData(element, 'drop-now', Language.get('wcf.attachment.dragAndDrop.dropNow'));
-                                       
-                                       element.addEventListener('dragover', function () { element.classList.add('active'); });
-                                       element.addEventListener('dragleave', function () { element.classList.remove('active'); });
-                                       element.addEventListener('drop', this._drop.bind(this));
-                                       
-                                       data.element = element;
-                               }
-                               
-                               editor.parentNode.insertBefore(element, editor);
-                               element.style.setProperty('top', editor.offsetTop + 'px', '');
-                       }).bind(this));
-               },
-               
-               /**
-                * Handles items dropped onto an editor's drop area
-                * 
-                * @param       {Event}         event           drop event
-                * @protected
-                */
-               _drop: function (event) {
-                       if (!_isFile) {
-                               return;
-                       }
-                       
-                       //noinspection JSUnresolvedVariable
-                       if (!event.dataTransfer || !event.dataTransfer.files.length) {
-                               return;
-                       }
-                       
-                       event.preventDefault();
-                       
-                       //noinspection JSCheckFunctionSignatures
-                       var elementId = elData(event.currentTarget, 'element-id');
-                       
-                       //noinspection JSUnresolvedVariable
-                       for (var i = 0, length = event.dataTransfer.files.length; i < length; i++) {
-                               //noinspection JSUnresolvedVariable
-                               EventHandler.fire('com.woltlab.wcf.redactor2', 'dragAndDrop_' + elementId, {
-                                       file: event.dataTransfer.files[i]
-                               });
-                       }
-                       
-                       // this will reset all drop areas
-                       this._dragLeave();
-               },
-               
-               /**
-                * Invoked whenever the item is no longer dragged or was dropped.
-                * 
-                * @protected
-                */
-               _dragLeave: function () {
-                       if (!_isDragging || !_isFile) {
-                               return;
-                       }
-                       
-                       if (_timerLeave !== null) {
-                               window.clearTimeout(_timerLeave);
-                       }
-                       
-                       _timerLeave = window.setTimeout(function () {
-                               if (!_isDragging) {
-                                       _dragArea.forEach(function (data) {
-                                               if (data.element && data.element.parentNode) {
-                                                       data.element.classList.remove('active');
-                                                       elRemove(data.element);
-                                               }
-                                       });
-                               }
-                               
-                               _timerLeave = null;
-                       }, 100);
-                       
-                       _isDragging = false;
-               },
-               
-               /**
-                * Handles the global drop event.
-                * 
-                * @param       {Event}         event
-                * @protected
-                */
-               _globalDrop: function (event) {
-                       if (event.target.closest('.redactor-layer') === null) {
-                               var eventData = { cancelDrop: true, event: event };
-                               _dragArea.forEach(function(data) {
-                                       //noinspection JSUnresolvedVariable
-                                       EventHandler.fire('com.woltlab.wcf.redactor2', 'dragAndDrop_globalDrop_' + data.editor.$element[0].id, eventData);
-                               });
-                               
-                               if (eventData.cancelDrop) {
-                                       event.preventDefault();
-                               }
-                       }
-                       
-                       this._dragLeave(event);
-               },
-               
-               /**
-                * Binds listeners to global events.
-                * 
-                * @protected
-                */
-               _setup: function () {
-                       // discard garbage event
-                       window.addEventListener('dragend', function (event) { event.preventDefault(); });
-                       
-                       window.addEventListener('dragover', this._dragOver.bind(this));
-                       window.addEventListener('dragleave', this._dragLeave.bind(this));
-                       window.addEventListener('drop', this._globalDrop.bind(this));
-                       
-                       _didInit = true;
-               }
-       };
-});
-
-/**
- * Generic interface for drag and Drop file uploads.
- * 
- * @author      Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module      WoltLabSuite/Core/Ui/DragAndDrop
- */
-define('WoltLabSuite/Core/Ui/DragAndDrop',['Core', 'EventHandler', 'WoltLabSuite/Core/Ui/Redactor/DragAndDrop'], function (Core, EventHandler, UiRedactorDragAndDrop) {
-       /**
-        * @exports     WoltLabSuite/Core/Ui/DragAndDrop
-        */
-       return {
-               /**
-                * @param       {Object}        options
-                */
-               register: function (options) {
-                       var uuid = Core.getUuid();
-                       options = Core.extend({
-                               element: '',
-                               elementId: '',
-                               onDrop: function(data) {
-                                       /* data: { file: File } */
-                               },
-                               onGlobalDrop: function (data) {
-                                       /* data: { cancelDrop: boolean, event: DragEvent } */
-                               }
-                       });
-                       
-                       EventHandler.add('com.woltlab.wcf.redactor2', 'dragAndDrop_' + options.elementId, options.onDrop);
-                       EventHandler.add('com.woltlab.wcf.redactor2', 'dragAndDrop_globalDrop_' + options.elementId, options.onGlobalDrop);
-                       
-                       UiRedactorDragAndDrop.init({
-                               uuid: uuid,
-                               $editor: [options.element],
-                               $element: [{id: options.elementId}]
-                       });
-               }
-       };
-});
-
-/**
- * Flexible UI element featuring both a list of items and an input field with suggestion support.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Suggestion
- */
-define('WoltLabSuite/Core/Ui/Suggestion',['Ajax', 'Core', 'Ui/SimpleDropdown'], function(Ajax, Core, UiSimpleDropdown) {
-       "use strict";
-       
-       /**
-        * @constructor
-        * @param       {string}                elementId       input element id
-        * @param       {Object}                options         option list
-        */
-       function UiSuggestion(elementId, options) { this.init(elementId, options); }
-       UiSuggestion.prototype = {
-               /**
-                * Initializes a new suggestion input.
-                * 
-                * @param       {string}                elementId       input element id
-                * @param       {Object}                options         option list
-                */
-               init: function(elementId, options) {
-                       this._dropdownMenu = null;
-                       this._value = '';
-                       
-                       this._element = elById(elementId);
-                       if (this._element === null) {
-                               throw new Error("Expected a valid element id.");
-                       }
-                       
-                       this._options = Core.extend({
-                               ajax: {
-                                       actionName: 'getSearchResultList',
-                                       className: '',
-                                       interfaceName: 'wcf\\data\\ISearchAction',
-                                       parameters: {
-                                               data: {}
-                                       }
-                               },
-                               
-                               // will be executed once a value from the dropdown has been selected
-                               callbackSelect: null,
-                               // list of excluded search values
-                               excludedSearchValues: [],
-                               // minimum number of characters required to trigger a search request
-                               threshold: 3
-                       }, options);
-                       
-                       if (typeof this._options.callbackSelect !== 'function') {
-                               throw new Error("Expected a valid callback for option 'callbackSelect'.");
-                       }
-                       
-                       this._element.addEventListener(WCF_CLICK_EVENT, function(event) { event.stopPropagation(); });
-                       this._element.addEventListener('keydown', this._keyDown.bind(this));
-                       this._element.addEventListener('keyup', this._keyUp.bind(this));
-               },
-               
-               /**
-                * Adds an excluded search value.
-                * 
-                * @param       {string}        value           excluded value
-                */
-               addExcludedValue: function(value) {
-                       if (this._options.excludedSearchValues.indexOf(value) === -1) {
-                               this._options.excludedSearchValues.push(value);
-                       }
-               },
-               
-               /**
-                * Removes an excluded search value.
-                * 
-                * @param       {string}        value           excluded value
-                */
-               removeExcludedValue: function(value) {
-                       var index = this._options.excludedSearchValues.indexOf(value);
-                       if (index !== -1) {
-                               this._options.excludedSearchValues.splice(index, 1);
-                       }
-               },
-               
-               /**
-                * Returns true if the suggestions are active.
-                * @return      {boolean}
-                */
-               isActive: function() {
-                       return (this._dropdownMenu !== null && UiSimpleDropdown.isOpen(this._element.id));
-               },
-               
-               /**
-                * Handles the keyboard navigation for interaction with the suggestion list.
-                * 
-                * @param       {object}        event           event object
-                */
-               _keyDown: function(event) {
-                       if (!this.isActive()) {
-                               return true;
-                       }
-                       
-                       if (event.keyCode !== 13 && event.keyCode !== 27 && event.keyCode !== 38 && event.keyCode !== 40) {
-                               return true;
-                       }
-                       
-                       var active, i = 0, length = this._dropdownMenu.childElementCount;
-                       while (i < length) {
-                               active = this._dropdownMenu.children[i];
-                               if (active.classList.contains('active')) {
-                                       break;
-                               }
-                               
-                               i++;
-                       }
-                       
-                       if (event.keyCode === 13) {
-                               // Enter
-                               UiSimpleDropdown.close(this._element.id);
-                               
-                               this._select(active);
-                       }
-                       else if (event.keyCode === 27) {
-                               if (UiSimpleDropdown.isOpen(this._element.id)) {
-                                       UiSimpleDropdown.close(this._element.id);
-                               }
-                               else {
-                                       // let the event pass through
-                                       return true;
-                               }
-                       }
-                       else {
-                               var index = 0;
-                               
-                               if (event.keyCode === 38) {
-                                       // ArrowUp
-                                       index = ((i === 0) ? length : i) - 1;
-                               }
-                               else if (event.keyCode === 40) {
-                                       // ArrowDown
-                                       index = i + 1;
-                                       if (index === length) index = 0;
-                               }
-                               
-                               if (index !== i) {
-                                       active.classList.remove('active');
-                                       this._dropdownMenu.children[index].classList.add('active');
-                               }
-                       }
-                       
-                       event.preventDefault();
-                       return false;
-               },
-               
-               /**
-                * Selects an item from the list.
-                * 
-                * @param       {(Element|Event)}       item    list item or event object
-                */
-               _select: function(item) {
-                       var isEvent = (item instanceof Event);
-                       if (isEvent) {
-                               item = item.currentTarget.parentNode;
-                       }
-                       
-                       var anchor = item.children[0];
-                       this._options.callbackSelect(this._element.id, { objectId: elData(anchor, 'object-id'), value: item.textContent, type: elData(anchor, 'type') });
-                       
-                       if (isEvent) {
-                               this._element.focus();
-                       }
-               },
-               
-               /**
-                * Performs a search for the input value unless it is below the threshold.
-                * 
-                * @param       {object}                event           event object
-                */
-               _keyUp: function(event) {
-                       var value = event.currentTarget.value.trim();
-                       
-                       if (this._value === value) {
-                               return;
-                       }
-                       else if (value.length < this._options.threshold) {
-                               if (this._dropdownMenu !== null) {
-                                       UiSimpleDropdown.close(this._element.id);
-                               }
-                               
-                               this._value = value;
-                               
-                               return;
-                       }
-                       
-                       this._value = value;
-                       
-                       Ajax.api(this, {
-                               parameters: {
-                                       data: {
-                                               excludedSearchValues: this._options.excludedSearchValues,
-                                               searchString: value
-                                       }
-                               }
-                       });
-               },
-               
-               _ajaxSetup: function() {
-                       return {
-                               data: this._options.ajax
-                       };
-               },
-               
-               /**
-                * Handles successful Ajax requests.
-                * 
-                * @param       {object}        data            response values
-                */
-               _ajaxSuccess: function(data) {
-                       if (this._dropdownMenu === null) {
-                               this._dropdownMenu = elCreate('div');
-                               this._dropdownMenu.className = 'dropdownMenu';
-                               
-                               UiSimpleDropdown.initFragment(this._element, this._dropdownMenu);
-                       }
-                       else {
-                               this._dropdownMenu.innerHTML = '';
-                       }
-                       
-                       if (data.returnValues.length) {
-                               var anchor, item, listItem;
-                               for (var i = 0, length = data.returnValues.length; i < length; i++) {
-                                       item = data.returnValues[i];
-                                       
-                                       anchor = elCreate('a');
-                                       if (item.icon) {
-                                               anchor.className = 'box16';
-                                               anchor.innerHTML = item.icon + ' <span></span>';
-                                               anchor.children[1].textContent = item.label;
-                                       }
-                                       else {
-                                               anchor.textContent = item.label;
-                                       }
-                                       elData(anchor, 'object-id', item.objectID);
-                                       if (item.type) elData(anchor, 'type', item.type);
-                                       anchor.addEventListener(WCF_CLICK_EVENT, this._select.bind(this));
-                                       
-                                       listItem = elCreate('li');
-                                       if (i === 0) listItem.className = 'active';
-                                       listItem.appendChild(anchor);
-                                       
-                                       this._dropdownMenu.appendChild(listItem);
-                               }
-                               
-                               UiSimpleDropdown.open(this._element.id, true);
-                       }
-                       else {
-                               UiSimpleDropdown.close(this._element.id);
-                       }
-               }
-       };
-       
-       return UiSuggestion;
-});
-
-/**
- * Flexible UI element featuring both a list of items and an input field with suggestion support.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/ItemList
- */
-define('WoltLabSuite/Core/Ui/ItemList',['Core', 'Dictionary', 'Language', 'Dom/Traverse', 'EventKey', 'WoltLabSuite/Core/Ui/Suggestion', 'Ui/SimpleDropdown'], function(Core, Dictionary, Language, DomTraverse, EventKey, UiSuggestion, UiSimpleDropdown) {
-       "use strict";
-       
-       var _activeId = '';
-       var _data = new Dictionary();
-       var _didInit = false;
-       
-       var _callbackKeyDown = null;
-       var _callbackKeyPress = null;
-       var _callbackKeyUp = null;
-       var _callbackPaste = null;
-       var _callbackRemoveItem = null;
-       var _callbackBlur = null;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/ItemList
-        */
-       return {
-               /**
-                * Initializes an item list.
-                * 
-                * The `values` argument must be empty or contain a list of strings or object, e.g.
-                * `['foo', 'bar']` or `[{ objectId: 1337, value: 'baz'}, {...}]`
-                * 
-                * @param       {string}        elementId       input element id
-                * @param       {Array}         values          list of existing values
-                * @param       {Object}        options         option list
-                */
-               init: function(elementId, values, options) {
-                       var element = elById(elementId);
-                       if (element === null) {
-                               throw new Error("Expected a valid element id, '" + elementId + "' is invalid.");
-                       }
-                       
-                       // remove data from previous instance
-                       if (_data.has(elementId)) {
-                               var tmp = _data.get(elementId);
-                               
-                               for (var key in tmp) {
-                                       if (tmp.hasOwnProperty(key)) {
-                                               var el = tmp[key];
-                                               if (el instanceof Element && el.parentNode) {
-                                                       elRemove(el);
-                                               }
-                                       }
-                               }
-                               
-                               UiSimpleDropdown.destroy(elementId);
-                               _data.delete(elementId);
-                       }
-                       
-                       options = Core.extend({
-                               // search parameters for suggestions
-                               ajax: {
-                                       actionName: 'getSearchResultList',
-                                       className: '',
-                                       data: {}
-                               },
-                               
-                               // list of excluded string values, e.g. `['ignore', 'these strings', 'when', 'searching']`
-                               excludedSearchValues: [],
-                               // maximum number of items this list may contain, `-1` for infinite
-                               maxItems: -1,
-                               // maximum length of an item value, `-1` for infinite
-                               maxLength: -1,
-                               // disallow custom values, only values offered by the suggestion dropdown are accepted
-                               restricted: false,
-                               
-                               // initial value will be interpreted as comma separated value and submitted as such
-                               isCSV: false,
-                               
-                               // will be invoked whenever the items change, receives the element id first and list of values second
-                               callbackChange: null,
-                               // callback once the form is about to be submitted
-                               callbackSubmit: null,
-                               // Callback for the custom shadow synchronization.
-                               callbackSyncShadow: null,
-                               // Callback to set values during the setup.
-                               callbackSetupValues: null,
-                               // value may contain the placeholder `{$objectId}`
-                               submitFieldName: ''
-                       }, options);
-                       
-                       var form = DomTraverse.parentByTag(element, 'FORM');
-                       if (form !== null) {
-                               if (options.isCSV === false) {
-                                       if (!options.submitFieldName.length && typeof options.callbackSubmit !== 'function') {
-                                               throw new Error("Expected a valid function for option 'callbackSubmit', a non-empty value for option 'submitFieldName' or enabling the option 'submitFieldCSV'.");
-                                       }
-                                       
-                                       form.addEventListener('submit', (function() {
-                                               var values = this.getValues(elementId);
-                                               if (options.submitFieldName.length) {
-                                                       var input;
-                                                       for (var i = 0, length = values.length; i < length; i++) {
-                                                               input = elCreate('input');
-                                                               input.type = 'hidden';
-                                                               input.name = options.submitFieldName.replace(/{$objectId}/, values[i].objectId);
-                                                               input.value = values[i].value;
-                                                               
-                                                               form.appendChild(input);
-                                                       }
-                                               }
-                                               else {
-                                                       options.callbackSubmit(form, values);
-                                               }
-                                       }).bind(this));
-                               }
-                       }
-                       
-                       this._setup();
-                       
-                       var data = this._createUI(element, options);
-                       //noinspection JSUnresolvedVariable
-                       var suggestion = new UiSuggestion(elementId, {
-                               ajax: options.ajax,
-                               callbackSelect: this._addItem.bind(this),
-                               excludedSearchValues: options.excludedSearchValues
-                       });
-                       
-                       _data.set(elementId, {
-                               dropdownMenu: null,
-                               element: data.element,
-                               list: data.list,
-                               listItem: data.element.parentNode,
-                               options: options,
-                               shadow: data.shadow,
-                               suggestion: suggestion
-                       });
-                       
-                       if (options.callbackSetupValues) {
-                               values = options.callbackSetupValues();
-                       }
-                       else {
-                               values = (data.values.length) ? data.values : values;
-                       }
-                       
-                       if (Array.isArray(values)) {
-                               var value;
-                               for (var i = 0, length = values.length; i < length; i++) {
-                                       value = values[i];
-                                       if (typeof value === 'string') {
-                                               value = { objectId: 0, value: value };
-                                       }
-                                       
-                                       this._addItem(elementId, value);
-                               }
-                       }
-               },
-               
-               /**
-                * Returns the list of current values.
-                * 
-                * @param       {string}        elementId       input element id
-                * @return      {Array}         list of objects containing object id and value
-                */
-               getValues: function(elementId) {
-                       if (!_data.has(elementId)) {
-                               throw new Error("Element id '" + elementId + "' is unknown.");
-                       }
-                       
-                       var data = _data.get(elementId);
-                       var values = [];
-                       elBySelAll('.item > span', data.list, function(span) {
-                               values.push({
-                                       objectId: ~~elData(span, 'object-id'),
-                                       value: span.textContent,
-                                       type: elData(span, 'type')
-                               });
-                       });
-                       
-                       return values;
-               },
-               
-               /**
-                * Sets the list of current values.
-                * 
-                * @param       {string}        elementId       input element id
-                * @param       {Array}         values          list of objects containing object id and value
-                */
-               setValues: function(elementId, values) {
-                       if (!_data.has(elementId)) {
-                               throw new Error("Element id '" + elementId + "' is unknown.");
-                       }
-                       
-                       var data = _data.get(elementId);
-                       
-                       // remove all existing items first
-                       var i, length;
-                       var items = DomTraverse.childrenByClass(data.list, 'item');
-                       for (i = 0, length = items.length; i < length; i++) {
-                               this._removeItem(null, items[i], true);
-                       }
-                       
-                       // add new items
-                       for (i = 0, length = values.length; i < length; i++) {
-                               this._addItem(elementId, values[i]);
-                       }
-               },
-               
-               /**
-                * Binds static event listeners.
-                */
-               _setup: function() {
-                       if (_didInit) {
-                               return;
-                       }
-                       
-                       _didInit = true;
-                       
-                       _callbackKeyDown = this._keyDown.bind(this);
-                       _callbackKeyPress = this._keyPress.bind(this);
-                       _callbackKeyUp = this._keyUp.bind(this);
-                       _callbackPaste = this._paste.bind(this);
-                       _callbackRemoveItem = this._removeItem.bind(this);
-                       _callbackBlur = this._blur.bind(this);
-               },
-               
-               /**
-                * Creates the DOM structure for target element. If `element` is a `<textarea>`
-                * it will be automatically replaced with an `<input>` element.
-                * 
-                * @param       {Element}       element         input element
-                * @param       {Object}        options         option list
-                */
-               _createUI: function(element, options) {
-                       var list = elCreate('ol');
-                       list.className = 'inputItemList' + (element.disabled ? ' disabled' : '');
-                       elData(list, 'element-id', element.id);
-                       list.addEventListener(WCF_CLICK_EVENT, function(event) {
-                               if (event.target === list) {
-                                       //noinspection JSUnresolvedFunction
-                                       element.focus();
-                               }
-                       });
-                       
-                       var listItem = elCreate('li');
-                       listItem.className = 'input';
-                       list.appendChild(listItem);
-                       
-                       element.addEventListener('keydown', _callbackKeyDown);
-                       element.addEventListener('keypress', _callbackKeyPress);
-                       element.addEventListener('keyup', _callbackKeyUp);
-                       element.addEventListener('paste', _callbackPaste);
-                       var hasFocus = element === document.activeElement;
-                       if (hasFocus) {
-                               //noinspection JSUnresolvedFunction
-                               element.blur();
-                       }
-                       element.addEventListener('blur', _callbackBlur);
-                       element.parentNode.insertBefore(list, element);
-                       listItem.appendChild(element);
-                       if (hasFocus) {
-                               window.setTimeout(function() {
-                                       //noinspection JSUnresolvedFunction
-                                       element.focus();
-                               }, 1);
-                       }
-                       
-                       if (options.maxLength !== -1) {
-                               elAttr(element, 'maxLength', options.maxLength);
-                       }
-                       
-                       var shadow = null, values = [];
-                       if (options.isCSV) {
-                               shadow = elCreate('input');
-                               shadow.className = 'itemListInputShadow';
-                               shadow.type = 'hidden';
-                               //noinspection JSUnresolvedVariable
-                               shadow.name = element.name;
-                               element.removeAttribute('name');
-                               
-                               list.parentNode.insertBefore(shadow, list);
-                               
-                               //noinspection JSUnresolvedVariable
-                               var value, tmp = element.value.split(',');
-                               for (var i = 0, length = tmp.length; i < length; i++) {
-                                       value = tmp[i].trim();
-                                       if (value.length) {
-                                               values.push(value);
-                                       }
-                               }
-                               
-                               if (element.nodeName === 'TEXTAREA') {
-                                       var inputElement = elCreate('input');
-                                       inputElement.type = 'text';
-                                       element.parentNode.insertBefore(inputElement, element);
-                                       inputElement.id = element.id;
-                                       
-                                       elRemove(element);
-                                       element = inputElement;
-                               }
-                       }
-                       
-                       return {
-                               element: element,
-                               list: list,
-                               shadow: shadow,
-                               values: values
-                       };
-               },
-               
-               /**
-                * Returns true if the input accepts new items.
-                * 
-                * @param       {string}        elementId       input element id
-                * @return      {boolean}       true if at least one more item can be added
-                * @protected
-                */
-               _acceptsNewItems: function (elementId) {
-                       var data = _data.get(elementId);
-                       if (data.options.maxItems === -1) {
-                               return true;
-                       }
-                       
-                       return (data.list.childElementCount - 1 < data.options.maxItems);
-               },
-               
-               /**
-                * Enforces the maximum number of items.
-                * 
-                * @param       {string}        elementId       input element id
-                */
-               _handleLimit: function(elementId) {
-                       var data = _data.get(elementId);
-                       if (this._acceptsNewItems(elementId)) {
-                               if (data.element.disabled) {
-                                       data.element.disabled = false;
-                                       data.element.removeAttribute('placeholder');
-                               }
-                       }
-                       else if (!data.element.disabled) {
-                               data.element.disabled = true;
-                               elAttr(data.element, 'placeholder', Language.get('wcf.global.form.input.maxItems'));
-                       }
-               },
-               
-               /**
-                * Sets the active item list id and handles keyboard access to remove an existing item.
-                * 
-                * @param       {object}        event           event object
-                */
-               _keyDown: function(event) {
-                       var input = event.currentTarget;
-                       var lastItem = input.parentNode.previousElementSibling;
-                       
-                       _activeId = input.id;
-                       
-                       if (event.keyCode === 8) {
-                               // 8 = [BACKSPACE]
-                               if (input.value.length === 0) {
-                                       if (lastItem !== null) {
-                                               if (lastItem.classList.contains('active')) {
-                                                       this._removeItem(null, lastItem);
-                                               }
-                                               else {
-                                                       lastItem.classList.add('active');
-                                               }
-                                       }
-                               }
-                       }
-                       else if (event.keyCode === 27) {
-                               // 27 = [ESC]
-                               if (lastItem !== null && lastItem.classList.contains('active')) {
-                                       lastItem.classList.remove('active');
-                               }
-                       }
-               },
-               
-               /**
-                * Handles the `[ENTER]` and `[,]` key to add an item to the list unless it is restricted.
-                * 
-                * @param       {Event}         event           event object
-                */
-               _keyPress: function(event) {
-                       if (EventKey.Enter(event) || EventKey.Comma(event)) {
-                               event.preventDefault();
-                               
-                               if (_data.get(event.currentTarget.id).options.restricted) {
-                                       // restricted item lists only allow results from the dropdown to be picked
-                                       return;
-                               }
-                               
-                               var value = event.currentTarget.value.trim();
-                               if (value.length) {
-                                       this._addItem(event.currentTarget.id, { objectId: 0, value: value });
-                               }
-                       }
-               },
-               
-               /**
-                * Splits comma-separated values being pasted into the input field.
-                * 
-                * @param       {Event}         event
-                * @protected
-                */
-               _paste: function (event) {
-                       var text = '';
-                       if (typeof window.clipboardData === 'object') {
-                               // IE11
-                               text = window.clipboardData.getData('Text');
-                       }
-                       else {
-                               text = event.clipboardData.getData('text/plain');
-                       }
-                       
-                       var element = event.currentTarget;
-                       var elementId = element.id;
-                       var maxLength = ~~elAttr(element, 'maxLength');
-                       
-                       text.split(/,/).forEach((function(item) {
-                               item = item.trim();
-                               if (maxLength && item.length > maxLength) {
-                                       // truncating items provides a better UX than throwing an error or silently discarding it
-                                       item = item.substr(0, maxLength);
-                               }
-                               
-                               if (item.length > 0 && this._acceptsNewItems(elementId)) {
-                                       this._addItem(elementId, {objectId: 0, value: item});
-                               }
-                       }).bind(this));
-                       
-                       event.preventDefault();
-               },
-               
-               /**
-                * Handles the keyup event to unmark an item for deletion.
-                * 
-                * @param       {object}        event           event object
-                */
-               _keyUp: function(event) {
-                       var input = event.currentTarget;
-                       
-                       if (input.value.length > 0) {
-                               var lastItem = input.parentNode.previousElementSibling;
-                               if (lastItem !== null) {
-                                       lastItem.classList.remove('active');
-                               }
-                       }
-               },
-               
-               /**
-                * Adds an item to the list.
-                * 
-                * @param       {string}        elementId       input element id
-                * @param       {object}        value           item value
-                */
-               _addItem: function(elementId, value) {
-                       var data = _data.get(elementId);
-                       
-                       var listItem = elCreate('li');
-                       listItem.className = 'item';
-                       
-                       var content = elCreate('span');
-                       content.className = 'content';
-                       elData(content, 'object-id', value.objectId);
-                       if (value.type) elData(content, 'type', value.type);
-                       content.textContent = value.value;
-                       listItem.appendChild(content);
-                       
-                       if (!data.element.disabled) {
-                               var button = elCreate('a');
-                               button.className = 'icon icon16 fa-times';
-                               button.addEventListener(WCF_CLICK_EVENT, _callbackRemoveItem);
-                               listItem.appendChild(button);
-                       }
-                       
-                       data.list.insertBefore(listItem, data.listItem);
-                       data.suggestion.addExcludedValue(value.value);
-                       data.element.value = '';
-                       
-                       if (!data.element.disabled) {
-                               this._handleLimit(elementId);
-                       }
-                       var values = this._syncShadow(data);
-                       
-                       if (typeof data.options.callbackChange === 'function') {
-                               if (values === null) values = this.getValues(elementId);
-                               data.options.callbackChange(elementId, values);
-                       }
-               },
-               
-               /**
-                * Removes an item from the list.
-                * 
-                * @param       {?object}       event           event object
-                * @param       {Element?}      item            list item
-                * @param       {boolean?}      noFocus         input element will not be focused if true
-                */
-               _removeItem: function(event, item, noFocus) {
-                       item = (event === null) ? item : event.currentTarget.parentNode;
-                       
-                       var parent = item.parentNode;
-                       //noinspection JSCheckFunctionSignatures
-                       var elementId = elData(parent, 'element-id');
-                       var data = _data.get(elementId);
-                       
-                       data.suggestion.removeExcludedValue(item.children[0].textContent);
-                       parent.removeChild(item);
-                       if (!noFocus) data.element.focus();
-                       
-                       this._handleLimit(elementId);
-                       var values = this._syncShadow(data);
-                       
-                       if (typeof data.options.callbackChange === 'function') {
-                               if (values === null) values = this.getValues(elementId);
-                               data.options.callbackChange(elementId, values);
-                       }
-               },
-               
-               /**
-                * Synchronizes the shadow input field with the current list item values.
-                * 
-                * @param       {object}        data            element data
-                */
-               _syncShadow: function(data) {
-                       if (!data.options.isCSV) return null;
-                       if (typeof data.options.callbackSyncShadow === 'function') {
-                               return data.options.callbackSyncShadow(data);
-                       }
-                       
-                       var value = '', values = this.getValues(data.element.id);
-                       for (var i = 0, length = values.length; i < length; i++) {
-                               value += (value.length ? ',' : '') + values[i].value;
-                       }
-                       
-                       data.shadow.value = value;
-                       
-                       return values;
-               },
-               
-               /**
-                * Handles the blur event.
-                *
-                * @param       {object}        event           event object
-                */
-               _blur: function(event) {
-                       var input = event.currentTarget;
-                       var data = _data.get(input.id);
-                       if (data.options.restricted) {
-                               // restricted item lists only allow results from the dropdown to be picked
-                               return;
-                       }
-                       
-                       var value = input.value.trim();
-                       if (value.length) {
-                               if (!data.suggestion || !data.suggestion.isActive()) {
-                                       this._addItem(input.id, { objectId: 0, value: value });
-                               }
-                       }
-               }
-       };
-});
-
-/**
- * Utility class to provide a 'Jump To' overlay. 
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Page/JumpTo
- */
-define('WoltLabSuite/Core/Ui/Page/JumpTo',['Language', 'ObjectMap', 'Ui/Dialog'], function(Language, ObjectMap, UiDialog) {
-       "use strict";
-       
-       var _activeElement = null;
-       var _buttonSubmit = null;
-       var _description = null;
-       var _elements = new ObjectMap();
-       var _input = null;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Page/JumpTo
-        */
-       var UiPageJumpTo = {
-               /**
-                * Initializes a 'Jump To' element.
-                * 
-                * @param       {Element}       element         trigger element
-                * @param       {function}      callback        callback function, receives the page number as first argument
-                */
-               init: function(element, callback) {
-                       callback = callback || null;
-                       if (callback === null) {
-                               var redirectUrl = elData(element, 'link');
-                               if (redirectUrl) {
-                                       callback = function(pageNo) {
-                                               window.location = redirectUrl.replace(/pageNo=%d/, 'pageNo=' + pageNo);
-                                       };
-                               }
-                               else {
-                                       callback = function() {};
-                               }
-                               
-                       }
-                       else if (typeof callback !== 'function') {
-                               throw new TypeError("Expected a valid function for parameter 'callback'.");
-                       }
-                       
-                       if (!_elements.has(element)) {
-                               elBySelAll('.jumpTo', element, (function(jumpTo) {
-                                       jumpTo.addEventListener(WCF_CLICK_EVENT, this._click.bind(this, element));
-                                       _elements.set(element, { callback: callback });
-                               }).bind(this));
-                       }
-               },
-               
-               /**
-                * Handles clicks on the trigger element.
-                * 
-                * @param       {Element}       element         trigger element
-                * @param       {object}        event           event object
-                */
-               _click: function(element, event) {
-                       _activeElement = element;
-                       
-                       if (typeof event === 'object') {
-                               event.preventDefault();
-                       }
-                       
-                       UiDialog.open(this);
-                       
-                       var pages = elData(element, 'pages');
-                       _input.value = pages;
-                       _input.setAttribute('max', pages);
-                       _input.select();
-                       
-                       _description.textContent = Language.get('wcf.page.jumpTo.description').replace(/#pages#/, pages);
-               },
-               
-               /**
-                * Handles changes to the page number input field.
-                * 
-                * @param       {object}        event           event object
-                */
-               _keyUp: function(event) {
-                       if (event.which === 13 && _buttonSubmit.disabled === false) {
-                               this._submit();
-                               return;
-                       }
-                       
-                       var pageNo = ~~_input.value;
-                       if (pageNo < 1 || pageNo > ~~elAttr(_input, 'max')) {
-                               _buttonSubmit.disabled = true;
-                       }
-                       else {
-                               _buttonSubmit.disabled = false;
-                       }
-               },
-               
-               /**
-                * Invokes the callback with the chosen page number as first argument.
-                * 
-                * @param       {object}        event           event object
-                */
-               _submit: function(event) {
-                       _elements.get(_activeElement).callback(~~_input.value);
-                       
-                       UiDialog.close(this);
-               },
-               
-               _dialogSetup: function() {
-                       var source = '<dl>'
-                                       + '<dt><label for="jsPaginationPageNo">' + Language.get('wcf.page.jumpTo') + '</label></dt>'
-                                       + '<dd>'
-                                               + '<input type="number" id="jsPaginationPageNo" value="1" min="1" max="1" class="tiny">'
-                                               + '<small></small>'
-                                       + '</dd>'
-                               + '</dl>'
-                               + '<div class="formSubmit">'
-                                       + '<button class="buttonPrimary">' + Language.get('wcf.global.button.submit') + '</button>'
-                               + '</div>';
-                       
-                       return {
-                               id: 'paginationOverlay',
-                               options: {
-                                       onSetup: (function(content) {
-                                               _input = elByTag('input', content)[0];
-                                               _input.addEventListener('keyup', this._keyUp.bind(this));
-                                               
-                                               _description = elByTag('small', content)[0];
-                                               
-                                               _buttonSubmit = elByTag('button', content)[0];
-                                               _buttonSubmit.addEventListener(WCF_CLICK_EVENT, this._submit.bind(this));
-                                       }).bind(this),
-                                       title: Language.get('wcf.global.page.pagination')
-                               },
-                               source: source
-                       };
-               }
-       };
-       
-       return UiPageJumpTo;
-});
-/**
- * Callback-based pagination.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Pagination
- */
-define('WoltLabSuite/Core/Ui/Pagination',['Core', 'Language', 'ObjectMap', 'StringUtil', 'WoltLabSuite/Core/Ui/Page/JumpTo'], function(Core, Language, ObjectMap, StringUtil, UiPageJumpTo) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function UiPagination(element, options) { this.init(element, options); }
-       UiPagination.prototype = {
-               /**
-                * maximum number of displayed page links, should match the PHP implementation
-                * @var {int}
-                */
-               SHOW_LINKS: 11,
-               
-               /**
-                * Initializes the pagination.
-                * 
-                * @param       {Element}       element         container element
-                * @param       {object}        options         list of initialization options
-                */
-               init: function(element, options) {
-                       this._element = element;
-                       this._options = Core.extend({
-                               activePage: 1,
-                               maxPage: 1,
-                               
-                               callbackShouldSwitch: null,
-                               callbackSwitch: null
-                       }, options);
-                       
-                       if (typeof this._options.callbackShouldSwitch !== 'function') this._options.callbackShouldSwitch = null;
-                       if (typeof this._options.callbackSwitch !== 'function') this._options.callbackSwitch = null;
-                       
-                       this._element.classList.add('pagination');
-                       
-                       this._rebuild(this._element);
-               },
-               
-               /**
-                * Rebuilds the entire pagination UI.
-                */
-               _rebuild: function() {
-                       var hasHiddenPages = false;
-                       
-                       // clear content
-                       this._element.innerHTML = '';
-                       
-                       var list = elCreate('ul'), link;
-                       
-                       var listItem = elCreate('li');
-                       listItem.className = 'skip';
-                       list.appendChild(listItem);
-                       
-                       var iconClassNames = 'icon icon24 fa-chevron-left';
-                       if (this._options.activePage > 1) {
-                               link = elCreate('a');
-                               link.className = iconClassNames + ' jsTooltip';
-                               link.href = '#';
-                               link.title = Language.get('wcf.global.page.previous');
-                               link.rel = 'prev';
-                               listItem.appendChild(link);
-                               
-                               link.addEventListener(WCF_CLICK_EVENT, this.switchPage.bind(this, this._options.activePage - 1));
-                       }
-                       else {
-                               listItem.innerHTML = '<span class="' + iconClassNames + '"></span>';
-                               listItem.classList.add('disabled');
-                       }
-                       
-                       // add first page
-                       list.appendChild(this._createLink(1));
-                       
-                       // calculate page links
-                       var maxLinks = this.SHOW_LINKS - 4;
-                       var linksBefore = this._options.activePage - 2;
-                       if (linksBefore < 0) linksBefore = 0;
-                       var linksAfter = this._options.maxPage - (this._options.activePage + 1);
-                       if (linksAfter < 0) linksAfter = 0;
-                       if (this._options.activePage > 1 && this._options.activePage < this._options.maxPage) maxLinks--;
-                       
-                       var half = maxLinks / 2;
-                       var left = this._options.activePage;
-                       var right = this._options.activePage;
-                       if (left < 1) left = 1;
-                       if (right < 1) right = 1;
-                       if (right > this._options.maxPage - 1) right = this._options.maxPage - 1;
-                       
-                       if (linksBefore >= half) {
-                               left -= half;
-                       }
-                       else {
-                               left -= linksBefore;
-                               right += half - linksBefore;
-                       }
-                       
-                       if (linksAfter >= half) {
-                               right += half;
-                       }
-                       else {
-                               right += linksAfter;
-                               left -= half - linksAfter;
-                       }
-                       
-                       right = Math.ceil(right);
-                       left = Math.ceil(left);
-                       if (left < 1) left = 1;
-                       if (right > this._options.maxPage) right = this._options.maxPage;
-                       
-                       // left ... links
-                       var jumpToHtml = '<a class="jsTooltip" title="' + Language.get('wcf.page.jumpTo') + '">&hellip;</a>';
-                       if (left > 1) {
-                               if (left - 1 < 2) {
-                                       list.appendChild(this._createLink(2));
-                               }
-                               else {
-                                       listItem = elCreate('li');
-                                       listItem.className = 'jumpTo';
-                                       listItem.innerHTML = jumpToHtml;
-                                       list.appendChild(listItem);
-                                       
-                                       hasHiddenPages = true;
-                               }
-                       }
-                       
-                       // visible links
-                       for (var i = left + 1; i < right; i++) {
-                               list.appendChild(this._createLink(i));
-                       }
-                       
-                       // right ... links
-                       if (right < this._options.maxPage) {
-                               if (this._options.maxPage - right < 2) {
-                                       list.appendChild(this._createLink(this._options.maxPage - 1));
-                               }
-                               else {
-                                       listItem = elCreate('li');
-                                       listItem.className = 'jumpTo';
-                                       listItem.innerHTML = jumpToHtml;
-                                       list.appendChild(listItem);
-                                       
-                                       hasHiddenPages = true;
-                               }
-                       }
-                       
-                       // add last page
-                       list.appendChild(this._createLink(this._options.maxPage));
-                       
-                       // add next button
-                       listItem = elCreate('li');
-                       listItem.className = 'skip';
-                       list.appendChild(listItem);
-                       
-                       iconClassNames = 'icon icon24 fa-chevron-right';
-                       if (this._options.activePage < this._options.maxPage) {
-                               link = elCreate('a');
-                               link.className = iconClassNames + ' jsTooltip';
-                               link.href = '#';
-                               link.title = Language.get('wcf.global.page.next');
-                               link.rel = 'next';
-                               listItem.appendChild(link);
-                               
-                               link.addEventListener(WCF_CLICK_EVENT, this.switchPage.bind(this, this._options.activePage + 1));
-                       }
-                       else {
-                               listItem.innerHTML = '<span class="' + iconClassNames + '"></span>';
-                               listItem.classList.add('disabled');
-                       }
-                       
-                       if (hasHiddenPages) {
-                               elData(list, 'pages', this._options.maxPage);
-                               
-                               UiPageJumpTo.init(list, this.switchPage.bind(this));
-                       }
-                       
-                       this._element.appendChild(list);
-               },
-               
-               /**
-                * Creates a link to a specific page.
-                * 
-                * @param       {int}           pageNo          page number
-                * @return      {Element}       link element
-                */
-               _createLink: function(pageNo) {
-                       var listItem = elCreate('li');
-                       if (pageNo !== this._options.activePage) {
-                               var link = elCreate('a');
-                               link.textContent = StringUtil.addThousandsSeparator(pageNo);
-                               link.addEventListener(WCF_CLICK_EVENT, this.switchPage.bind(this, pageNo));
-                               listItem.appendChild(link);
-                       }
-                       else {
-                               listItem.classList.add('active');
-                               listItem.innerHTML = '<span>' + StringUtil.addThousandsSeparator(pageNo) + '</span><span class="invisible">' + Language.get('wcf.page.pagePosition', { pageNo: pageNo, pages: this._options.maxPage }) + '</span>';
-                       }
-                       
-                       return listItem;
-               },
-               
-               /**
-                * Returns the active page.
-                *
-                * @return      {integer}
-                */
-               getActivePage: function() {
-                       return this._options.activePage;
-               },
-               
-               /**
-                * Returns the pagination Ui element.
-                * 
-                * @return      {HTMLElement}
-                */
-               getElement: function() {
-                       return this._element;
-               },
-               
-               /**
-                * Returns the maximum page.
-                * 
-                * @return      {integer}
-                */
-               getMaxPage: function() {
-                       return this._options.maxPage;
-               },
-               
-               /**
-                * Switches to given page number.
-                * 
-                * @param       {int}           pageNo          page number
-                * @param       {object}        event           event object
-                */
-               switchPage: function(pageNo, event) {
-                       if (typeof event === 'object') {
-                               event.preventDefault();
-                               
-                               // force tooltip to vanish and strip positioning
-                               if (event.currentTarget && elData(event.currentTarget, 'tooltip')) {
-                                       var tooltip = elById('balloonTooltip');
-                                       if (tooltip) {
-                                               Core.triggerEvent(event.currentTarget, 'mouseleave');
-                                               tooltip.style.removeProperty('top');
-                                               tooltip.style.removeProperty('bottom');
-                                       }
-                               }
-                       }
-                       
-                       pageNo = ~~pageNo;
-                       
-                       if (pageNo > 0 && this._options.activePage !== pageNo && pageNo <= this._options.maxPage) {
-                               if (this._options.callbackShouldSwitch !== null) {
-                                       if (this._options.callbackShouldSwitch(pageNo) !== true) {
-                                               return;
-                                       }
-                               }
-                               
-                               this._options.activePage = pageNo;
-                               this._rebuild();
-                               
-                               if (this._options.callbackSwitch !== null) {
-                                       this._options.callbackSwitch(pageNo);
-                               }
-                       }
-               }
-       };
-       
-       return UiPagination;
-});
-
-/**
- * Smoothly scrolls to an element while accounting for potential sticky headers.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Scroll
- */
-define('WoltLabSuite/Core/Ui/Scroll',['Dom/Util'], function(DomUtil) {
-       "use strict";
-       
-       var _callback = null;
-       var _callbackScroll = null;
-       var _offset = null;
-       var _timeoutScroll = null;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Scroll
-        */
-       return {
-               /**
-                * Scrolls to target element, optionally invoking the provided callback once scrolling has ended.
-                * 
-                * @param       {Element}       element         target element
-                * @param       {function=}     callback        callback invoked once scrolling has ended
-                */
-               element: function(element, callback) {
-                       if (!(element instanceof Element)) {
-                               throw new TypeError("Expected a valid DOM element.");
-                       }
-                       else if (callback !== undefined && typeof callback !== 'function') {
-                               throw new TypeError("Expected a valid callback function.");
-                       }
-                       else if (!document.body.contains(element)) {
-                               throw new Error("Element must be part of the visible DOM.");
-                       }
-                       else if (_callback !== null) {
-                               throw new Error("Cannot scroll to element, a concurrent request is running.");
-                       }
-                       
-                       if (callback) {
-                               _callback = callback;
-                               
-                               if (_callbackScroll === null) {
-                                       _callbackScroll = this._onScroll.bind(this);
-                               }
-                               
-                               window.addEventListener('scroll', _callbackScroll);
-                       }
-                       
-                       var y = DomUtil.offset(element).top;
-                       if (_offset === null) {
-                               _offset = 50;
-                               var pageHeader = elById('pageHeaderPanel');
-                               if (pageHeader !== null) {
-                                       var position = window.getComputedStyle(pageHeader).position;
-                                       if (position === 'fixed' || position === 'static') {
-                                               _offset = pageHeader.offsetHeight;
-                                       }
-                                       else {
-                                               _offset = 0;
-                                       }
-                               }
-                       }
-                       
-                       if (_offset > 0) {
-                               if (y <= _offset) {
-                                       y = 0;
-                               }
-                               else {
-                                       // add an offset to account for a sticky header
-                                       y -= _offset;
-                               }
-                       }
-                       
-                       var offset = window.pageYOffset;
-                       
-                       window.scrollTo({
-                               left: 0,
-                               top: y,
-                               behavior: 'smooth'
-                       });
-                       
-                       window.setTimeout((function () {
-                               // no scrolling took place
-                               if (offset === window.pageYOffset) {
-                                       this._onScroll();
-                               }
-                       }).bind(this), 100);
-               },
-               
-               /**
-                * Monitors scroll event to only execute the callback once scrolling has ended.
-                * 
-                * @protected
-                */
-               _onScroll: function() {
-                       if (_timeoutScroll !== null) window.clearTimeout(_timeoutScroll);
-                       
-                       _timeoutScroll = window.setTimeout(function() {
-                               if (_callback !== null) _callback();
-                               
-                               window.removeEventListener('scroll', _callbackScroll);
-                               _callback = null;
-                               _timeoutScroll = null;
-                       }, 100);
-               }
-       };
-});
-
-/**
- * Initializes modules required for media list view.
- *
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Controller/Media/List
- */
-define('WoltLabSuite/Core/Controller/Media/List',[
-               'Dom/ChangeListener',
-               'EventHandler',
-               'WoltLabSuite/Core/Controller/Clipboard',
-               'WoltLabSuite/Core/Media/Clipboard',
-               'WoltLabSuite/Core/Media/Editor',
-               'WoltLabSuite/Core/Media/List/Upload'
-       ],
-       function(
-               DomChangeListener,
-               EventHandler,
-               Clipboard,
-               MediaClipboard,
-               MediaEditor,
-               MediaListUpload
-       ) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _addButtonEventListeners: function() {},
-                       _deleteCallback: function() {},
-                       _deleteMedia: function(mediaIds) {},
-                       _edit: function() {}
-               };
-               return Fake;
-       }
-       
-       var _mediaEditor;
-       var _tableBody = elById('mediaListTableBody');
-       var _clipboardObjectIds = [];
-       var _upload;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Controller/Media/List
-        */
-       return {
-               init: function(options) {
-                       options = options || {};
-                       _upload = new MediaListUpload('uploadButton', 'mediaListTableBody', {
-                               categoryId: options.categoryId,
-                               multiple: true
-                       });
-                       
-                       MediaClipboard.init(
-                               'wcf\\acp\\page\\MediaListPage',
-                               options.hasMarkedItems || false,
-                               this
-                       );
-                       
-                       EventHandler.add('com.woltlab.wcf.media.upload', 'removedErroneousUploadRow', this._deleteCallback.bind(this));
-                       
-                       var deleteAction = new WCF.Action.Delete('wcf\\data\\media\\MediaAction', '.jsMediaRow');
-                       deleteAction.setCallback(this._deleteCallback);
-                       
-                       _mediaEditor = new MediaEditor({
-                               _editorSuccess: function(media, oldCategoryId) {
-                                       if (media.categoryID != oldCategoryId) {
-                                               window.setTimeout(function() {
-                                                       window.location.reload();
-                                               }, 500);
-                                       }
-                               }
-                       });
-                       
-                       this._addButtonEventListeners();
-                       
-                       DomChangeListener.add('WoltLabSuite/Core/Controller/Media/List', this._addButtonEventListeners.bind(this));
-                       
-                       EventHandler.add('com.woltlab.wcf.media.upload', 'success', this._openEditorAfterUpload.bind(this));
-               },
-               
-               /**
-                * Adds the `click` event listeners to the media edit icons in
-                * new media table rows.
-                */
-               _addButtonEventListeners: function() {
-                       var buttons = elByClass('jsMediaEditButton', _tableBody), button;
-                       while (buttons.length) {
-                               button = buttons[0];
-                               button.classList.remove('jsMediaEditButton');
-                               button.addEventListener(WCF_CLICK_EVENT, this._edit.bind(this));
-                       }
-               },
-               
-               /**
-                * Is triggered after media files have been deleted using the delete icon.
-                * 
-                * @param       {int[]?}        objectIds
-                */
-               _deleteCallback: function(objectIds) {
-                       var tableRowCount = elByTag('tr', _tableBody).length;
-                       if (objectIds.length === undefined) {
-                               if (!tableRowCount) {
-                                       window.location.reload();
-                               }
-                       }
-                       else if (objectIds.length === tableRowCount) {
-                               // table is empty, reload page
-                               window.location.reload();
-                       }
-                       else {
-                               Clipboard.reload.bind(Clipboard)
-                       }
-               },
-               
-               /**
-                * Is called when a media edit icon is clicked.
-                * 
-                * @param       {Event}         event
-                */
-               _edit: function(event) {
-                       _mediaEditor.edit(elData(event.currentTarget, 'object-id'));
-               },
-               
-               /**
-                * Opens the media editor after uploading a single file.
-                *
-                * @param       {object}        data    upload event data
-                * @since       5.2
-                */
-               _openEditorAfterUpload: function(data) {
-                       if (data.upload === _upload && !data.isMultiFileUpload && !_upload.hasPendingUploads()) {
-                               var keys = Object.keys(data.media);
-                               
-                               if (keys.length) {
-                                       _mediaEditor.edit(this._media.get(~~data.media[keys[0]].mediaID));
-                               }
-                       }
-               },
-               
-               /**
-                * Is called after the media files with the given ids have been deleted via clipboard.
-                * 
-                * @param       {int[]}         mediaIds        ids of deleted media files
-                */
-               clipboardDeleteMedia: function(mediaIds) {
-                       var mediaRows = elByClass('jsMediaRow');
-                       for (var i = 0; i < mediaRows.length; i++) {
-                               var media = mediaRows[i];
-                               var mediaID = ~~elData(elByClass('jsClipboardItem', media)[0], 'object-id');
-                               
-                               if (mediaIds.indexOf(mediaID) !== -1) {
-                                       elRemove(media);
-                                       i--;
-                               }
-                       }
-                       
-                       if (!mediaRows.length) {
-                               window.location.reload();
-                       }
-               }
-       }
-});
-/**
- * Handles dismissible user notices.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Controller/Notice/Dismiss
- */
-define('WoltLabSuite/Core/Controller/Notice/Dismiss',['Ajax'], function(Ajax) {
-       "use strict";
-       
-       /**
-        * @exports     WoltLabSuite/Core/Controller/Notice/Dismiss
-        */
-       var ControllerNoticeDismiss = {
-               /**
-                * Initializes dismiss buttons.
-                */
-               setup: function() {
-                       var buttons = elByClass('jsDismissNoticeButton');
-                       
-                       if (buttons.length) {
-                               var clickCallback = this._click.bind(this);
-                               for (var i = 0, length = buttons.length; i < length; i++) {
-                                       buttons[i].addEventListener(WCF_CLICK_EVENT, clickCallback);
-                               }
-                       }
-               },
-               
-               /**
-                * Sends a request to dismiss a notice and removes it afterwards.
-                */
-               _click: function(event) {
-                       var button = event.currentTarget;
-                       
-                       Ajax.apiOnce({
-                               data: {
-                                       actionName: 'dismiss',
-                                       className: 'wcf\\data\\notice\\NoticeAction',
-                                       objectIDs: [ elData(button, 'object-id') ]
-                               },
-                               success: function() {
-                                       elRemove(button.parentNode);
-                               }
-                       });
-               }
-       };
-       
-       return ControllerNoticeDismiss;
-});
-
-/**
- * Manages form field dependencies.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Dependency/Manager
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Dependency/Manager',['Dictionary', 'Dom/ChangeListener', 'EventHandler', 'List', 'Dom/Traverse', 'Dom/Util', 'ObjectMap'], function(Dictionary, DomChangeListener, EventHandler, List, DomTraverse, DomUtil, ObjectMap) {
-       "use strict";
-       
-       /**
-        * is `true` if containters are currently checked for their availablility, otherwise `false`
-        * @type        {boolean}
-        * @private
-        */
-       var _checkingContainers = false;
-       
-       /**
-        * is `true` if containter will be checked again after the current check for their availablility
-        * has finished, otherwise `false`
-        * @type        {boolean}
-        * @private
-        */
-       var _checkContainersAgain = true;
-       
-       /**
-        * list of containers hidden due to their own dependencies
-        * @type        {List}
-        * @private
-        */
-       var _dependencyHiddenNodes = new List();
-       
-       /**
-        * list of fields for which event listeners have been registered
-        * @type        {Dictionary}
-        * @private
-        */
-       var _fields = new Dictionary();
-       
-       /**
-        * list of registered forms
-        * @type        {List}
-        * @private
-        */
-       var _forms = new List();
-       
-       /**
-        * list of dependencies grouped by the dependent node they belong to
-        * @type        {Dictionary}
-        * @private
-        */
-       var _nodeDependencies = new Dictionary();
-       
-       /**
-        * cache of validation-related properties of hidden form fields
-        * @type        {ObjectMap}
-        * @private
-        */
-       var _validatedFieldProperties = new ObjectMap();
-       
-       return {
-               /**
-                * Hides the given node because of its own dependencies.
-                * 
-                * @param       {HTMLElement}   node    hidden node
-                * @protected
-                */
-               _hide: function(node) {
-                       elHide(node);
-                       _dependencyHiddenNodes.add(node);
-                       
-                       // also hide tab menu entry
-                       if (node.classList.contains('tabMenuContent')) {
-                               elBySelAll('li', DomTraverse.prevByClass(node, 'tabMenu'), function(tabLink) {
-                                       if (elData(tabLink, 'name') === elData(node, 'name')) {
-                                               elHide(tabLink);
-                                       }
-                               });
-                       }
-                       
-                       elBySelAll('[max], [maxlength], [min], [required]', node, function(validatedField) {
-                               var properties = new Dictionary();
-                               
-                               var max = elAttr(validatedField, 'max');
-                               if (max) {
-                                       properties.set('max', max);
-                                       validatedField.removeAttribute('max');
-                               }
-                               
-                               var maxlength = elAttr(validatedField, 'maxlength');
-                               if (maxlength) {
-                                       properties.set('maxlength', maxlength);
-                                       validatedField.removeAttribute('maxlength');
-                               }
-                               
-                               var min = elAttr(validatedField, 'min');
-                               if (min) {
-                                       properties.set('min', min);
-                                       validatedField.removeAttribute('min');
-                               }
-                               
-                               if (validatedField.required) {
-                                       properties.set('required', true);
-                                       validatedField.removeAttribute('required');
-                               }
-                               
-                               _validatedFieldProperties.set(validatedField, properties);
-                       });
-               },
-               
-               /**
-                * Shows the given node because of its own dependencies.
-                * 
-                * @param       {HTMLElement}   node    shown node
-                * @protected
-                */
-               _show: function(node) {
-                       elShow(node);
-                       _dependencyHiddenNodes.delete(node);
-                       
-                       // also show tab menu entry
-                       if (node.classList.contains('tabMenuContent')) {
-                               elBySelAll('li', DomTraverse.prevByClass(node, 'tabMenu'), function(tabLink) {
-                                       if (elData(tabLink, 'name') === elData(node, 'name')) {
-                                               elShow(tabLink);
-                                       }
-                               });
-                       }
-                       
-                       elBySelAll('input, select', node, function(validatedField) {
-                               // if a container is shown, ignore all fields that
-                               // have a hidden parent element within the container
-                               var parentNode = validatedField.parentNode;
-                               while (parentNode !== node && parentNode.style.getPropertyValue('display') !== 'none') {
-                                       parentNode = parentNode.parentNode;
-                               }
-                               
-                               if (parentNode === node && _validatedFieldProperties.has(validatedField)) {
-                                       var properties = _validatedFieldProperties.get(validatedField);
-                                       
-                                       if (properties.has('max')) {
-                                               elAttr(validatedField, 'max', properties.get('max'));
-                                       }
-                                       if (properties.has('maxlength')) {
-                                               elAttr(validatedField, 'maxlength', properties.get('maxlength'));
-                                       }
-                                       if (properties.has('min')) {
-                                               elAttr(validatedField, 'min', properties.get('min'));
-                                       }
-                                       if (properties.has('required')) {
-                                               elAttr(validatedField, 'required', '');
-                                       }
-                                       
-                                       _validatedFieldProperties.delete(validatedField);
-                               }
-                       });
-               },
-               
-               /**
-                * Registers a new form field dependency.
-                * 
-                * @param       {WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract}      dependency      new dependency
-                */
-               addDependency: function(dependency) {
-                       var dependentNode = dependency.getDependentNode();
-                       if (!_nodeDependencies.has(dependentNode.id)) {
-                               _nodeDependencies.set(dependentNode.id, [dependency]);
-                       }
-                       else {
-                               _nodeDependencies.get(dependentNode.id).push(dependency);
-                       }
-                       
-                       var fields = dependency.getFields();
-                       for (var i = 0, length = fields.length; i < length; i++) {
-                               var field = fields[i];
-                               var id = DomUtil.identify(field);
-                               
-                               if (!_fields.has(id)) {
-                                       _fields.set(id, field);
-                                       
-                                       if (field.tagName === 'INPUT' && (field.type === 'checkbox' || field.type === 'radio')) {
-                                               field.addEventListener('change', this.checkDependencies.bind(this));
-                                       }
-                                       else {
-                                               field.addEventListener('input', this.checkDependencies.bind(this));
-                                       }
-                               }
-                       }
-               },
-               
-               /**
-                * Checks if all dependencies are met.
-                */
-               checkDependencies: function() {
-                       var obsoleteNodes = [];
-                       
-                       _nodeDependencies.forEach(function(nodeDependencies, nodeId) {
-                               var dependentNode = elById(nodeId);
-                               
-                               // check if dependent node still exists
-                               if (dependentNode === null) {
-                                       obsoleteNodes.push(dependentNode);
-                                       return;
-                               }
-                               
-                               for (var i = 0, length = nodeDependencies.length; i < length; i++) {
-                                       // if any dependency is not met, hide the element
-                                       if (!nodeDependencies[i].checkDependency()) {
-                                               this._hide(dependentNode);
-                                               return;
-                                       }
-                               }
-                               
-                               // all node dependency is met
-                               this._show(dependentNode);
-                       }.bind(this));
-                       
-                       // delete dependencies for removed elements
-                       for (var i = 0, length = obsoleteNodes.length; i < length; i++) {
-                               _nodeDependencies.delete(obsoleteNodes.id);
-                       }
-                       
-                       this.checkContainers();
-               },
-               
-               /**
-                * Adds the given callback to the list of callbacks called when checking containers.
-                * 
-                * @param       {function}      callback
-                */
-               addContainerCheckCallback: function(callback) {
-                       if (typeof callback !== 'function') {
-                               throw new TypeError("Expected a valid callback for parameter 'callback'.");
-                       }
-                       
-                       EventHandler.add('com.woltlab.wcf.form.builder.dependency', 'checkContainers', callback);
-               },
-               
-               /**
-                * Checks the containers for their availability.
-                * 
-                * If this function is called while containers are currently checked, the containers
-                * will be checked after the current check has been finished completely.
-                */
-               checkContainers: function() {
-                       // check if containers are currently being checked
-                       if (_checkingContainers === true) {
-                               // and if that is the case, calling this method indicates, that after the current round,
-                               // containters should be checked to properly propagate changes in children to their parents
-                               _checkContainersAgain = true;
-                               
-                               return;
-                       }
-                       
-                       // starting to check containers also resets the flag to check containers again after the current check 
-                       _checkingContainers = true;
-                       _checkContainersAgain = false;
-                       
-                       EventHandler.fire('com.woltlab.wcf.form.builder.dependency', 'checkContainers');
-                       
-                       // finish checking containers and check if containters should be checked again
-                       _checkingContainers = false;
-                       if (_checkContainersAgain) {
-                               this.checkContainers();
-                       }
-               },
-               
-               /**
-                * Returns `true` if the given node has been hidden because of its own dependencies.
-                * 
-                * @param       {HTMLElement}   node    checked node
-                * @return      {boolean}
-                */
-               isHiddenByDependencies: function(node) {
-                       if (_dependencyHiddenNodes.has(node)) {
-                               return true;
-                       }
-                       
-                       var returnValue = false;
-                       _dependencyHiddenNodes.forEach(function(hiddenNode) {
-                               if (DomUtil.contains(hiddenNode, node)) {
-                                       returnValue = true;
-                               }
-                       });
-                       
-                       return returnValue;
-               },
-               
-               /**
-                * Registers the form with the given id with the dependency manager.
-                * 
-                * @param       {string}        formId          id of register form
-                * @throws      {Error}                         if given form id is invalid or has already been registered
-                */
-               register: function(formId) {
-                       var form = elById(formId);
-                       
-                       if (form === null) {
-                               throw new Error("Unknown element with id '" + formId + "'");
-                       }
-                       
-                       if (_forms.has(form)) {
-                               throw new Error("Form with id '" + formId + "' has already been registered.");
-                       }
-                       
-                       _forms.add(form);
-               },
-               
-               /**
-                * Unregisters the form with the given id and all of its dependencies.
-                * 
-                * @param       {string}        formId          id of unregistered form
-                */
-               unregister: function(formId) {
-                       var form = elById(formId);
-                       
-                       if (form === null) {
-                               throw new Error("Unknown element with id '" + formId + "'");
-                       }
-                       
-                       if (!_forms.has(form)) {
-                               throw new Error("Form with id '" + formId + "' has not been registered.");
-                       }
-                       
-                       _forms.delete(form);
-                       
-                       _dependencyHiddenNodes.forEach(function(hiddenNode) {
-                               if (form.contains(hiddenNode)) {
-                                       _dependencyHiddenNodes.delete(hiddenNode);
-                               }
-                       });
-                       _nodeDependencies.forEach(function(dependencies, nodeId) {
-                               if (form.contains(elById(nodeId))) {
-                                       _nodeDependencies.delete(nodeId);
-                               }
-                               
-                               for (var i = 0, length = dependencies.length; i < length; i++) {
-                                       var fields = dependencies[i].getFields();
-                                       for (var j = 0, length = fields.length; j < length; j++) {
-                                               var field = fields[j];
-                                               
-                                               _fields.delete(field.id);
-                                               
-                                               _validatedFieldProperties.delete(field);
-                                       }
-                               }
-                       });
-               }
-       };
-});
-
-/**
- * Data handler for a form builder field in an Ajax form.
- *
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Field
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Field',[], function() {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function FormBuilderField(fieldId) {
-               this.init(fieldId);
-       };
-       FormBuilderField.prototype = {
-               /**
-                * Initializes the form field.
-                * 
-                * @param       {string}        fieldId         id of the relevant form builder field
-                */
-               init: function(fieldId) {
-                       this._fieldId = fieldId;
-                       
-                       this._readField();
-               },
-               
-               /**
-                * Returns the current data of the field or a promise returning the current data
-                * of the field.
-                * 
-                * @return      {Promise|data}
-                */
-               _getData: function() {
-                       throw new Error("Missing implementation of WoltLabSuite/Core/Form/Builder/Field/Field._getData!");
-               },
-               
-               /**
-                * Reads the field HTML element.
-                */
-               _readField: function() {
-                       this._field = elById(this._fieldId);
-                       
-                       if (this._field === null) {
-                               throw new Error("Unknown field with id '" + this._fieldId + "'.");
-                       }
-               },
-               
-               /**
-                * Destroys the field.
-                * 
-                * This function is useful for remove registered elements from other APIs like dialogs.
-                */
-               destroy: function() {
-                       // does nothing
-               },
-               
-               /**
-                * Returns a promise returning the current data of the field.
-                * 
-                * @return      {Promise}
-                */
-               getData: function() {
-                       return Promise.resolve(this._getData());
-               },
-               
-               /**
-                * Returns the id of the field.
-                * 
-                * @return      {string}
-                */
-               getId: function() {
-                       return this._fieldId;
-               }
-       };
-       
-       return FormBuilderField;
-});
-
-/**
- * Manager for registered Ajax forms and its fields that can be used to retrieve the current data
- * of the registered forms.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Manager
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Manager',[
-       'Core',
-       'Dictionary',
-       './Field/Dependency/Manager',
-       './Field/Field'
-], function(
-       Core,
-       Dictionary,
-       FormBuilderFieldDependencyManager,
-       FormBuilderField
-) {
-       "use strict";
-       
-       var _fields = new Dictionary();
-       var _forms = new Dictionary();
-       
-       return {
-               /**
-                * Returns a promise returning the data of the form with the given id.
-                * 
-                * @param       {string}        formId
-                * @return      {Promise}
-                */
-               getData: function(formId) {
-                       if (!this.hasForm(formId)) {
-                               throw new Error("Unknown form with id '" + formId + "'.");
-                       }
-                       
-                       var promises = [];
-                       
-                       _fields.get(formId).forEach(function(field) {
-                               var fieldData = field.getData();
-                               
-                               if (!(fieldData instanceof Promise)) {
-                                       throw new TypeError("Data for field with id '" + field.getId() + "' is no promise.");
-                               }
-                               
-                               promises.push(fieldData);
-                       });
-                       
-                       return Promise.all(promises).then(function(promiseData) {
-                               var data = {};
-                               
-                               for (var i = 0, length = promiseData.length; i < length; i++) {
-                                       data = Core.extend(data, promiseData[i]);
-                               }
-                               
-                               return data;
-                       });
-               },
-               
-               /**
-                * Returns the registered form with given id.
-                * 
-                * @param       {string}        formId
-                * @return      {HTMLElement}
-                */
-               getForm: function(formId) {
-                       if (!this.hasForm(formId)) {
-                               throw new Error("Unknown form with id '" + formId + "'.");
-                       }
-                       
-                       return _forms.get(formId);
-               },
-               
-               /**
-                * Returns `true` if a field with the given id has been registered for the form with
-                * the given id and `false` otherwise.
-                * 
-                * @param       {string}        formId
-                * @param       {string}        fieldId
-                * @return      {boolean}
-                */
-               hasField: function(formId, fieldId) {
-                       if (!this.hasForm(formId)) {
-                               throw new Error("Unknown form with id '" + formId + "'.");
-                       }
-                       
-                       return _fields.get(formId).has(fieldId);
-               },
-               
-               /**
-                * Returns `true` if a form with the given id has been registered and `false`
-                * otherwise.
-                * 
-                * @param       {string}        formId
-                * @return      {boolean}
-                */
-               hasForm: function(formId) {
-                       return _forms.has(formId);
-               },
-               
-               /**
-                * Registers the given field for the form with the given id.
-                * 
-                * @param       {string}                                        formId
-                * @param       {WoltLabSuite/Core/Form/Builder/Field/Field}    field
-                */
-               registerField: function(formId, field) {
-                       if (!this.hasForm(formId)) {
-                               throw new Error("Unknown form with id '" + formId + "'.");
-                       }
-                       
-                       if (!(field instanceof FormBuilderField)) {
-                               throw new Error("Add field is no instance of 'WoltLabSuite/Core/Form/Builder/Field/Field'.");
-                       }
-                       
-                       var fieldId = field.getId();
-                       
-                       if (this.hasField(formId, fieldId)) {
-                               throw new Error("Form field with id '" + fieldId + "' has already been registered for form with id '" + fieldId + "'.");
-                       }
-                       
-                       _fields.get(formId).set(fieldId, field);
-               },
-               
-               /**
-                * Registers the form with the given id.
-                * 
-                * @param       {string}        formId
-                */
-               registerForm: function(formId) {
-                       if (this.hasForm(formId)) {
-                               throw new Error("Form with id '" + formId + "' has already been registered.");
-                       }
-                       
-                       var form = elById(formId);
-                       if (form === null) {
-                               throw new Error("Unknown form with id '" + formId + "'.");
-                       }
-                       
-                       _forms.set(formId, form);
-                       _fields.set(formId, new Dictionary());
-               },
-               
-               /**
-                * Unregisters the form with the given id.
-                * 
-                * @param       {string}        formId
-                */
-               unregisterForm: function(formId) {
-                       if (!this.hasForm(formId)) {
-                               throw new Error("Unknown form with id '" + formId + "'.");
-                       }
-                       
-                       _forms.delete(formId);
-                       
-                       _fields.get(formId).forEach(function(field) {
-                               field.destroy();
-                       });
-                       
-                       _fields.delete(formId);
-                       
-                       FormBuilderFieldDependencyManager.unregister(formId);
-               }
-       };
-});
-
-/**
- * Provides API to easily create a dialog form created by form builder.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Dialog
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Dialog',['Ajax', 'Core', './Manager', 'Ui/Dialog'], function(Ajax, Core, FormBuilderManager, UiDialog) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function FormBuilderDialog(dialogId, className, actionName, options) {
-               this.init(dialogId, className, actionName, options);
-       };
-       FormBuilderDialog.prototype = {
-               /**
-                * Initializes the dialog.
-                * 
-                * @param       {string}        dialogId
-                * @param       {string}        className
-                * @param       {string}        actionName
-                * @param       {{actionParameters: object, destoryOnClose: boolean, dialog: object}}   options
-                */
-               init: function(dialogId, className, actionName, options) {
-                       this._dialogId = dialogId;
-                       this._className = className;
-                       this._actionName = actionName;
-                       this._options = Core.extend({
-                               actionParameters: {},
-                               destroyOnClose: false,
-                               usesDboAction: this._className.match(/\w+\\data\\/)
-                       }, options);
-                       this._options.dialog = Core.extend(this._options.dialog || {}, {
-                               onClose: this._dialogOnClose.bind(this)
-                       });
-                       
-                       this._formId = '';
-                       this._dialogContent = '';
-               },
-               
-               /**
-                * Returns the data for Ajax to setup the Ajax/Request object.
-                * 
-                * @return      {object}        setup data for Ajax/Request object
-                */
-               _ajaxSetup: function() {
-                       var options = {
-                               data: {
-                                       actionName: this._actionName,
-                                       className: this._className,
-                                       parameters: this._options.actionParameters
-                               }
-                       };
-                       
-                       // by default, `AJAXProxyAction` is used which relies on an `IDatabaseObjectAction`
-                       // object; if no such object is used but an `IAJAXInvokeAction` object,
-                       // `AJAXInvokeAction` has to be used
-                       if (!this._options.usesDboAction) {
-                               options.url = 'index.php?ajax-invoke/&t=' + SECURITY_TOKEN;
-                               options.withCredentials = true;
-                       }
-                       
-                       return options;
-               },
-               
-               /**
-                * Handles successful Ajax requests.
-                *
-                * @param       {object}        data    response data
-                */
-               _ajaxSuccess: function(data) {
-                       switch (data.actionName) {
-                               case this._actionName:
-                                       if (data.returnValues === undefined) {
-                                               throw new Error("Missing return data.");
-                                       }
-                                       else if (data.returnValues.dialog === undefined) {
-                                               throw new Error("Missing dialog template in return data.");
-                                       }
-                                       else if (data.returnValues.formId === undefined) {
-                                               throw new Error("Missing form id in return data.");
-                                       }
-                                       
-                                       this._openDialogContent(data.returnValues.formId, data.returnValues.dialog);
-                                       
-                                       break;
-                                       
-                               case this._options.submitActionName:
-                                       // if the validation failed, the dialog is shown again
-                                       if (data.returnValues && data.returnValues.formId && data.returnValues.dialog) {
-                                               if (data.returnValues.formId !== this._formId) {
-                                                       throw new Error("Mismatch between form ids: expected '" + this._formId + "' but got '" + data.returnValues.formId + "'.");
-                                               }
-                                               
-                                               this._openDialogContent(data.returnValues.formId, data.returnValues.dialog);
-                                       }
-                                       else {
-                                               this.destroy();
-                                               
-                                               if (typeof this._options.successCallback === 'function') {
-                                                       this._options.successCallback(data.returnValues || {});
-                                               }
-                                       }
-                                       
-                                       break;
-                                       
-                               default:
-                                       throw new Error("Cannot handle action '" + data.actionName + "'.");
-                       }
-               },
-               
-               /**
-                * Is called when clicking on the dialog form's close button.
-                */
-               _closeDialog: function() {
-                       UiDialog.close(this);
-               },
-               
-               /**
-                * Is called by the dialog API when the dialog is closed.
-                */
-               _dialogOnClose: function() {
-                       if (this._options.destroyOnClose) {
-                               this.destroy();
-                       }
-               },
-               
-               /**
-                * Returns the data used to setup the dialog.
-                * 
-                * @return      {object}        setup data
-                */
-               _dialogSetup: function() {
-                       return {
-                               id: this._dialogId,
-                               options : this._options.dialog,
-                               source: this._dialogContent
-                       };
-               },
-               
-               /**
-                * Is called by the dialog API when the dialog form is submitted.
-                */
-               _dialogSubmit: function() {
-                       this.getData().then(this._submitForm.bind(this));
-               },
-               
-               /**
-                * Opens the form dialog with the given form content.
-                * 
-                * @param       {string}        formId
-                * @param       {string}        dialogContent
-                */
-               _openDialogContent: function(formId, dialogContent) {
-                       this.destroy(true);
-                       
-                       this._formId = formId;
-                       this._dialogContent = dialogContent;
-                       
-                       var dialogData = UiDialog.open(this, this._dialogContent);
-                       
-                       var cancelButton = elBySel('button[data-type=cancel]', dialogData.content);
-                       if (cancelButton !== null && !elDataBool(cancelButton, 'has-event-listener')) {
-                               cancelButton.addEventListener('click', this._closeDialog.bind(this));
-                               elData(cancelButton, 'has-event-listener', 1);
-                       }
-               },
-               
-               /**
-                * Submits the form with the given form data.
-                * 
-                * @param       {object}        formData
-                */
-               _submitForm: function(formData) {
-                       var submitButton = elBySel('button[data-type=submit]',  UiDialog.getDialog(this).content);
-                       
-                       if (typeof this._options.onSubmit === 'function') {
-                               this._options.onSubmit(formData, submitButton);
-                       }
-                       else if (typeof this._options.submitActionName === 'string') {
-                               submitButton.disabled = true;
-                               
-                               Ajax.api(this, {
-                                       actionName: this._options.submitActionName,
-                                       parameters: {
-                                               data: formData,
-                                               formId: this._formId
-                                       }
-                               });
-                       }
-               },
-               
-               /**
-                * Destroys the dialog.
-                * 
-                * @param       {boolean}       ignoreDialog    if `true`, the actual dialog is not destroyed, only the form is
-                */
-               destroy: function(ignoreDialog) {
-                       if (this._formId !== '') {
-                               if (FormBuilderManager.hasForm(this._formId)) {
-                                       FormBuilderManager.unregisterForm(this._formId);
-                               }
-                               
-                               if (ignoreDialog !== true) {
-                                       UiDialog.destroy(this);
-                               }
-                       }
-               },
-               
-               /**
-                * Returns a promise that all of the dialog form's data.
-                * 
-                * @return      {Promise}
-                */
-               getData: function() {
-                       if (this._formId === '') {
-                               throw new Error("Form has not been requested yet.");
-                       }
-                       
-                       return FormBuilderManager.getData(this._formId);
-               },
-               
-               /**
-                * Opens the dialog form.
-                */
-               open: function() {
-                       if (UiDialog.getDialog(this._dialogId)) {
-                               UiDialog.openStatic(this._dialogId);
-                       }
-                       else {
-                               Ajax.api(this);
-                       }
-               }
-       };
-       
-       return FormBuilderDialog;
-});
-
-/**
- * Provides the media search for the media manager.
- *
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Media/Manager/Search
- */
-define('WoltLabSuite/Core/Media/Manager/Search',['Ajax', 'Core', 'Dom/Traverse', 'Dom/Util', 'EventKey', 'Language', 'Ui/SimpleDropdown'], function(Ajax, Core, DomTraverse, DomUtil, EventKey, Language, UiSimpleDropdown) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       _ajaxSetup: function() {},
-                       _ajaxSuccess: function() {},
-                       _cancelSearch: function() {},
-                       _keyPress: function() {},
-                       _search: function() {},
-                       hideSearch: function() {},
-                       resetSearch: function() {},
-                       showSearch: function() {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * @constructor
-        */
-       function MediaManagerSearch(mediaManager) {
-               this._mediaManager = mediaManager;
-               this._searchMode = false;
-               
-               this._searchContainer = elByClass('mediaManagerSearch', mediaManager.getDialog())[0];
-               this._input = elByClass('mediaManagerSearchField', mediaManager.getDialog())[0];
-               this._input.addEventListener('keypress', this._keyPress.bind(this));
-               
-               this._cancelButton = elByClass('mediaManagerSearchCancelButton', mediaManager.getDialog())[0];
-               this._cancelButton.addEventListener(WCF_CLICK_EVENT, this._cancelSearch.bind(this));
-       }
-       MediaManagerSearch.prototype = {
-               /**
-                * Returns the data for Ajax to setup the Ajax/Request object.
-                *
-                * @return      {object}        setup data for Ajax/Request object
-                */
-               _ajaxSetup: function() {
-                       return {
-                               data: {
-                                       actionName: 'getSearchResultList',
-                                       className: 'wcf\\data\\media\\MediaAction',
-                                       interfaceName: 'wcf\\data\\ISearchAction'
-                               }
-                       };
-               },
-               
-               /**
-                * Handles successful AJAX requests.
-                *
-                * @param       {object}        data    response data
-                */
-               _ajaxSuccess: function(data) {
-                       this._mediaManager.setMedia(data.returnValues.media || { }, data.returnValues.template || '', {
-                               pageCount: data.returnValues.pageCount || 0,
-                               pageNo: data.returnValues.pageNo || 0
-                       });
-                       
-                       elByClass('dialogContent', this._mediaManager.getDialog())[0].scrollTop = 0;
-               },
-               
-               /**
-                * Cancels the search after clicking on the cancel search button.
-                */
-               _cancelSearch: function() {
-                       if (this._searchMode) {
-                               this._searchMode = false;
-                               
-                               this.resetSearch();
-                               this._mediaManager.resetMedia();
-                       }
-               },
-               
-               /**
-                * Hides the search string threshold error.
-                */
-               _hideStringThresholdError: function() {
-                       var innerInfo = DomTraverse.childByClass(this._input.parentNode.parentNode, 'innerInfo');
-                       if (innerInfo) {
-                               elHide(innerInfo);
-                       }
-               },
-               
-               /**
-                * Handles the `[ENTER]` key to submit the form.
-                *
-                * @param       {Event}         event           event object
-                */
-               _keyPress: function(event) {
-                       if (EventKey.Enter(event)) {
-                               event.preventDefault();
-                               
-                               if (this._input.value.length >= this._mediaManager.getOption('minSearchLength')) {
-                                       this._hideStringThresholdError();
-                                       
-                                       this.search();
-                               }
-                               else {
-                                       this._showStringThresholdError();
-                               }
-                       }
-               },
-               
-               /**
-                * Shows the search string threshold error.
-                */
-               _showStringThresholdError: function() {
-                       var innerInfo = DomTraverse.childByClass(this._input.parentNode.parentNode, 'innerInfo');
-                       if (innerInfo) {
-                               elShow(innerInfo);
-                       }
-                       else {
-                               innerInfo = elCreate('p');
-                               innerInfo.className = 'innerInfo';
-                               innerInfo.textContent = Language.get('wcf.media.search.info.searchStringThreshold', {
-                                       minSearchLength: this._mediaManager.getOption('minSearchLength')
-                               });
-                               
-                               DomUtil.insertAfter(innerInfo, this._input.parentNode);
-                       }
-               },
-               
-               /**
-                * Hides the media search.
-                */
-               hideSearch: function() {
-                       elHide(this._searchContainer);
-               },
-               
-               /**
-                * Resets the media search.
-                */
-               resetSearch: function() {
-                       this._input.value = '';
-               },
-               
-               /**
-                * Shows the media search.
-                */
-               showSearch: function() {
-                       elShow(this._searchContainer);
-               },
-               
-               /**
-                * Sends an AJAX request to fetch search results.
-                * 
-                * @param       {integer}       pageNo
-                */
-               search: function(pageNo) {
-                       if (typeof pageNo !== "number") {
-                               pageNo = 1;
-                       }
-                       
-                       var searchString = this._input.value;
-                       if (searchString && this._input.value.length < this._mediaManager.getOption('minSearchLength')) {
-                               this._showStringThresholdError();
-                               
-                               searchString = '';
-                       }
-                       else {
-                               this._hideStringThresholdError();
-                       }
-                       
-                       this._searchMode = true;
-                       
-                       Ajax.api(this, {
-                               parameters: {
-                                       categoryID: this._mediaManager.getCategoryId(),
-                                       imagesOnly: this._mediaManager.getOption('imagesOnly'),
-                                       mode: this._mediaManager.getMode(),
-                                       pageNo: pageNo,
-                                       searchString: searchString
-                               }
-                       });
-               },
-       };
-       
-       return MediaManagerSearch;
-});
-
-/**
- * Provides the media manager dialog.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Media/Manager/Base
- */
-define(
-       'WoltLabSuite/Core/Media/Manager/Base',[
-               'Core',                     'Dictionary',               'Dom/ChangeListener',              'Dom/Traverse',
-               'Dom/Util',                 'EventHandler',             'Language',                        'List',
-               'Permission',               'Ui/Dialog',                'Ui/Notification',                 'WoltLabSuite/Core/Controller/Clipboard',
-               'WoltLabSuite/Core/Media/Editor', 'WoltLabSuite/Core/Media/Upload', 'WoltLabSuite/Core/Media/Manager/Search', 'StringUtil',
-               'WoltLabSuite/Core/Ui/Pagination',
-               'WoltLabSuite/Core/Media/Clipboard'
-       ],
-       function(
-               Core,                        Dictionary,                 DomChangeListener,                 DomTraverse,
-               DomUtil,                     EventHandler,               Language,                          List,
-               Permission,                  UiDialog,                   UiNotification,                    Clipboard,
-               MediaEditor,                 MediaUpload,                MediaManagerSearch,                StringUtil,
-               UiPagination,
-               MediaClipboard
-       )
-{
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       _addButtonEventListeners: function() {},
-                       _click: function() {},
-                       _dialogClose: function() {},
-                       _dialogInit: function() {},
-                       _dialogSetup: function() {},
-                       _dialogShow: function() {},
-                       _editMedia: function() {},
-                       _editorClose: function() {},
-                       _editorSuccess: function() {},
-                       _removeClipboardCheckboxes: function() {},
-                       _setMedia: function() {},
-                       addMedia: function() {},
-                       clipboardDeleteMedia: function() {},
-                       getDialog: function() {},
-                       getMode: function() {},
-                       getOption: function() {},
-                       removeMedia: function() {},
-                       resetMedia: function() {},
-                       setMedia: function() {},
-                       setupMediaElement: function() {}
-               };
-               return Fake;
-       }
-       
-       var _mediaManagerCounter = 0;
-       
-       /**
-        * @constructor
-        */
-       function MediaManagerBase(options) {
-               this._options = Core.extend({
-                       dialogTitle: Language.get('wcf.media.manager'),
-                       imagesOnly: false,
-                       minSearchLength: 3
-               }, options);
-               
-               this._id = 'mediaManager' + _mediaManagerCounter++;
-               this._listItems = new Dictionary();
-               this._media = new Dictionary();
-               this._mediaManagerMediaList = null;
-               this._search = null;
-               this._upload = null;
-               this._forceClipboard = false;
-               this._hadInitiallyMarkedItems = false;
-               this._pagination = null;
-               
-               if (Permission.get('admin.content.cms.canManageMedia')) {
-                       this._mediaEditor = new MediaEditor(this);
-               }
-               
-               DomChangeListener.add('WoltLabSuite/Core/Media/Manager', this._addButtonEventListeners.bind(this));
-               
-               EventHandler.add('com.woltlab.wcf.media.upload', 'success', this._openEditorAfterUpload.bind(this));
-       }
-       MediaManagerBase.prototype = {
-               /**
-                * Adds click event listeners to media buttons.
-                */
-               _addButtonEventListeners: function() {
-                       if (!this._mediaManagerMediaList) return;
-                       
-                       var listItems = DomTraverse.childrenByTag(this._mediaManagerMediaList, 'LI');
-                       for (var i = 0, length = listItems.length; i < length; i++) {
-                               var listItem = listItems[i];
-                               
-                               if (Permission.get('admin.content.cms.canManageMedia')) {
-                                       var editIcon = elByClass('jsMediaEditButton', listItem)[0];
-                                       if (editIcon) {
-                                               editIcon.classList.remove('jsMediaEditButton');
-                                               editIcon.addEventListener(WCF_CLICK_EVENT, this._editMedia.bind(this));
-                                       }
-                               }
-                       }
-               },
-               
-               /**
-                * Is called when a new category is selected.
-                */
-               _categoryChange: function() {
-                       this._search.search();
-               },
-               
-               /**
-                * Handles clicks on the media manager button.
-                * 
-                * @param       {object}        event   event object
-                */
-               _click: function(event) {
-                       event.preventDefault();
-                       
-                       UiDialog.open(this);
-               },
-               
-               /**
-                * Is called if the media manager dialog is closed.
-                */
-               _dialogClose: function() {
-                       // only show media clipboard if editor is open
-                       if (Permission.get('admin.content.cms.canManageMedia') || this._forceClipboard) {
-                               Clipboard.hideEditor('com.woltlab.wcf.media');
-                       }
-               },
-               
-               /**
-                * Initializes the dialog when first loaded.
-                *
-                * @param       {string}        content         dialog content
-                * @param       {object}        data            AJAX request's response data
-                */
-               _dialogInit: function(content, data) {
-                       // store media data locally
-                       var media = data.returnValues.media || { };
-                       for (var mediaId in media) {
-                               if (objOwns(media, mediaId)) {
-                                       this._media.set(~~mediaId, media[mediaId]);
-                               }
-                       }
-                       
-                       this._initPagination(~~data.returnValues.pageCount);
-                       
-                       this._hadInitiallyMarkedItems = data.returnValues.hasMarkedItems;
-               },
-               
-               /**
-                * Returns all data to setup the media manager dialog.
-                * 
-                * @return      {object}        dialog setup data
-                */
-               _dialogSetup: function() {
-                       return {
-                               id: this._id,
-                               options: {
-                                       onClose: this._dialogClose.bind(this),
-                                       onShow: this._dialogShow.bind(this),
-                                       title: this._options.dialogTitle
-                               },
-                               source: {
-                                       after: this._dialogInit.bind(this),
-                                       data: {
-                                               actionName: 'getManagementDialog',
-                                               className: 'wcf\\data\\media\\MediaAction',
-                                               parameters: {
-                                                       mode: this.getMode(),
-                                                       imagesOnly: this._options.imagesOnly
-                                               }
-                                       }
-                               }
-                       };
-               },
-               
-               /**
-                * Is called if the media manager dialog is shown.
-                */
-               _dialogShow: function() {
-                       if (!this._mediaManagerMediaList) {
-                               var dialog = this.getDialog();
-                               
-                               this._mediaManagerMediaList = elByClass('mediaManagerMediaList', dialog)[0];
-                               
-                               this._mediaCategorySelect = elBySel('.mediaManagerCategoryList > select', dialog);
-                               if (this._mediaCategorySelect) {
-                                       this._mediaCategorySelect.addEventListener('change', this._categoryChange.bind(this));
-                               }
-                               
-                               // store list items locally
-                               var listItems = DomTraverse.childrenByTag(this._mediaManagerMediaList, 'LI');
-                               for (var i = 0, length = listItems.length; i < length; i++) {
-                                       var listItem = listItems[i];
-                                       
-                                       this._listItems.set(~~elData(listItem, 'object-id'), listItem);
-                               }
-                               
-                               if (Permission.get('admin.content.cms.canManageMedia')) {
-                                       var uploadButton = elByClass('mediaManagerMediaUploadButton', UiDialog.getDialog(this).dialog)[0];
-                                       this._upload = new MediaUpload(DomUtil.identify(uploadButton), DomUtil.identify(this._mediaManagerMediaList), {
-                                               mediaManager: this
-                                       });
-                                       
-                                       var deleteAction = new WCF.Action.Delete('wcf\\data\\media\\MediaAction', '.mediaFile');
-                                       deleteAction._didTriggerEffect = function(element) {
-                                               this.removeMedia(elData(element[0], 'object-id'));
-                                       }.bind(this);
-                               }
-                               
-                               if (Permission.get('admin.content.cms.canManageMedia') || this._forceClipboard) {
-                                       MediaClipboard.init(
-                                               'menuManagerDialog-' + this.getMode(),
-                                               this._hadInitiallyMarkedItems ? true : false,
-                                               this
-                                       );
-                               }
-                               else {
-                                       this._removeClipboardCheckboxes();
-                               }
-                               
-                               this._search = new MediaManagerSearch(this);
-                               
-                               if (!listItems.length) {
-                                       this._search.hideSearch();
-                               }
-                       }
-                       
-                       // only show media clipboard if editor is open
-                       if (Permission.get('admin.content.cms.canManageMedia') || this._forceClipboard) {
-                               Clipboard.showEditor('com.woltlab.wcf.media');
-                       }
-               },
-               
-               /**
-                * Opens the media editor for a media file.
-                * 
-                * @param       {Event}         event           event object for clicks on edit icons
-                */
-               _editMedia: function(event) {
-                       if (!Permission.get('admin.content.cms.canManageMedia')) {
-                               throw new Error("You are not allowed to edit media files.");
-                       }
-                       
-                       UiDialog.close(this);
-                       
-                       this._mediaEditor.edit(this._media.get(~~elData(event.currentTarget, 'object-id')));
-               },
-               
-               /**
-                * Re-opens the manager dialog after closing the editor dialog.
-                */
-               _editorClose: function() {
-                       UiDialog.open(this);
-               },
-               
-               /**
-                * Re-opens the manager dialog and updates the media data after
-                * successfully editing a media file.
-                * 
-                * @param       {object}        media           updated media file data
-                * @param       {integer}       oldCategoryId   old category id
-                */
-               _editorSuccess: function(media, oldCategoryId) {
-                       // if the category changed of media changed and category
-                       // is selected, check if media list needs to be refreshed
-                       if (this._mediaCategorySelect) {
-                               var selectedCategoryId = ~~this._mediaCategorySelect.value;
-                               
-                               if (selectedCategoryId) {
-                                       var newCategoryId = ~~media.categoryID;
-                                       
-                                       if (oldCategoryId != newCategoryId && (oldCategoryId == selectedCategoryId || newCategoryId == selectedCategoryId)) {
-                                               this._search.search();
-                                       }
-                               }
-                       }
-                       
-                       UiDialog.open(this);
-                       
-                       this._media.set(~~media.mediaID, media);
-                       
-                       var listItem = this._listItems.get(~~media.mediaID);
-                       var p = elByClass('mediaTitle', listItem)[0];
-                       if (media.isMultilingual) {
-                               p.textContent = media.title[LANGUAGE_ID] || media.filename;
-                       }
-                       else {
-                               p.textContent = media.title[media.languageID] || media.filename;
-                       }
-               },
-               
-               /**
-                * Initializes the dialog pagination.
-                *
-                * @param       {integer}       pageCount
-                * @param       {integer}       pageNo
-                */
-               _initPagination: function(pageCount, pageNo) {
-                       if (pageNo === undefined) pageNo = 1;
-                       
-                       if (pageCount > 1) {
-                               var newPagination = elCreate('div');
-                               newPagination.className = 'paginationBottom jsPagination';
-                               DomUtil.replaceElement(elBySel('.jsPagination', UiDialog.getDialog(this).content), newPagination);
-                               
-                               this._pagination = new UiPagination(newPagination, {
-                                       activePage: pageNo,
-                                       callbackSwitch: this._search.search.bind(this._search),
-                                       maxPage: pageCount
-                               });
-                       }
-                       else if (this._pagination) {
-                               elHide(this._pagination.getElement());
-                       }
-               },
-               
-               /**
-                * Removes all media clipboard checkboxes.
-                */
-               _removeClipboardCheckboxes: function() {
-                       var checkboxes = elByClass('mediaCheckbox', this._mediaManagerMediaList);
-                       while (checkboxes.length) {
-                               elRemove(checkboxes[0]);
-                       }
-               },
-               
-               /**
-                * Opens the media editor after uploading a single file.
-                * 
-                * @param       {object}        data    upload event data
-                * @since       5.2
-                */
-               _openEditorAfterUpload: function(data) {
-                       if (data.upload === this._upload && !data.isMultiFileUpload && !this._upload.hasPendingUploads()) {
-                               var keys = Object.keys(data.media);
-                               
-                               if (keys.length) {
-                                       UiDialog.close(this);
-                                       
-                                       this._mediaEditor.edit(this._media.get(~~data.media[keys[0]].mediaID));
-                               }
-                       }
-               },
-               
-               /**
-                * Sets the displayed media (after a search).
-                * 
-                * @param       {Dictionary}    media           media to be set as active
-                */
-               _setMedia: function(media) {
-                       if (Core.isPlainObject(media)) {
-                               this._media = Dictionary.fromObject(media);
-                       }
-                       else {
-                               this._media = media;
-                       }
-                       
-                       var info = DomTraverse.nextByClass(this._mediaManagerMediaList, 'info');
-                       
-                       if (this._media.size) {
-                               if (info) {
-                                       elHide(info);
-                               }
-                       }
-                       else {
-                               if (info === null) {
-                                       info = elCreate('p');
-                                       info.className = 'info';
-                                       info.textContent = Language.get('wcf.media.search.noResults');
-                               }
-                               
-                               elShow(info);
-                               DomUtil.insertAfter(info, this._mediaManagerMediaList);
-                       }
-                       
-                       var mediaListItems = DomTraverse.childrenByTag(this._mediaManagerMediaList, 'LI');
-                       for (var i = 0, length = mediaListItems.length; i < length; i++) {
-                               var listItem = mediaListItems[i];
-                               
-                               if (!this._media.has(elData(listItem, 'object-id'))) {
-                                       elHide(listItem);
-                               }
-                               else {
-                                       elShow(listItem);
-                               }
-                       }
-                       
-                       DomChangeListener.trigger();
-                       
-                       if (Permission.get('admin.content.cms.canManageMedia') || this._forceClipboard) {
-                               Clipboard.reload();
-                       }
-                       else {
-                               this._removeClipboardCheckboxes();
-                       }
-               },
-               
-               /**
-                * Adds a media file to the manager.
-                * 
-                * @param       {object}        media           data of the media file
-                * @param       {Element}       listItem        list item representing the file
-                */
-               addMedia: function(media, listItem) {
-                       if (!media.languageID) media.isMultilingual = 1;
-                       
-                       this._media.set(~~media.mediaID, media);
-                       this._listItems.set(~~media.mediaID, listItem);
-                       
-                       if (this._listItems.size === 1) {
-                               this._search.showSearch();
-                       }
-               },
-               
-               /**
-                * Is called after the media files with the given ids have been deleted via clipboard.
-                * 
-                * @param       {int[]}         mediaIds        ids of deleted media files
-                */
-               clipboardDeleteMedia: function(mediaIds) {
-                       for (var i = 0, length = mediaIds.length; i < length; i++) {
-                               this.removeMedia(~~mediaIds[i], true);
-                       }
-                       
-                       UiNotification.show();
-               },
-               
-               /**
-                * Returns the id of the currently selected category or `0` if no category is selected.
-                * 
-                * @return      {integer}
-                */
-               getCategoryId: function() {
-                       if (this._mediaCategorySelect) {
-                               return this._mediaCategorySelect.value;
-                       }
-                       
-                       return 0;
-               },
-               
-               /**
-                * Returns the media manager dialog element.
-                * 
-                * @return      {Element}       media manager dialog
-                */
-               getDialog: function() {
-                       return UiDialog.getDialog(this).dialog;
-               },
-               
-               /**
-                * Returns the mode of the media manager.
-                *
-                * @return      {string}
-                */
-               getMode: function() {
-                       return '';
-               },
-               
-               /**
-                * Returns the media manager option with the given name.
-                * 
-                * @param       {string}        name            option name
-                * @return      {mixed}         option value or null
-                */
-               getOption: function(name) {
-                       if (this._options[name]) {
-                               return this._options[name];
-                       }
-                       
-                       return null;
-               },
-               
-               /**
-                * Removes a media file.
-                *
-                * @param       {int}                   mediaId         id of the removed media file
-                */
-               removeMedia: function(mediaId) {
-                       if (this._listItems.has(mediaId)) {
-                               // remove list item
-                               try {
-                                       elRemove(this._listItems.get(mediaId));
-                               }
-                               catch (e) {
-                                       // ignore errors if item has already been removed like by WCF.Action.Delete
-                               }
-                               
-                               this._listItems.delete(mediaId);
-                               this._media.delete(mediaId);
-                       }
-               },
-               
-               /**
-                * Changes the displayed media to the previously displayed media.
-                */
-               resetMedia: function() {
-                       // calling WoltLabSuite/Core/Media/Manager/Search.search() reloads the first page of the dialog
-                       this._search.search();
-               },
-               
-               /**
-                * Sets the media files currently displayed.
-                * 
-                * @param       {object}        media           media data
-                * @param       {string}        template        
-                * @param       {object}        additionalData
-                */
-               setMedia: function(media, template, additionalData) {
-                       var hasMedia = false;
-                       for (var mediaId in media) {
-                               if (objOwns(media, mediaId)) {
-                                       hasMedia = true;
-                               }
-                       }
-                       
-                       var newListItems = [];
-                       if (hasMedia) {
-                               var ul = elCreate('ul');
-                               ul.innerHTML = template;
-                               
-                               var listItems = DomTraverse.childrenByTag(ul, 'LI');
-                               for (var i = 0, length = listItems.length; i < length; i++) {
-                                       var listItem = listItems[i];
-                                       if (!this._listItems.has(~~elData(listItem, 'object-id'))) {
-                                               this._listItems.set(elData(listItem, 'object-id'), listItem);
-                                               
-                                               this._mediaManagerMediaList.appendChild(listItem);
-                                       }
-                               }
-                       }
-                       
-                       this._initPagination(additionalData.pageCount, additionalData.pageNo);
-                       
-                       this._setMedia(media);
-               },
-               
-               /**
-                * Sets up a new media element.
-                * 
-                * @param       {object}        media           data of the media file
-                * @param       {HTMLElement}   mediaElement    element representing the media file
-                */
-               setupMediaElement: function(media, mediaElement) {
-                       var mediaInformation = DomTraverse.childByClass(mediaElement, 'mediaInformation');
-                       
-                       var buttonGroupNavigation = elCreate('nav');
-                       buttonGroupNavigation.className = 'jsMobileNavigation buttonGroupNavigation';
-                       mediaInformation.parentNode.appendChild(buttonGroupNavigation);
-                       
-                       var buttons = elCreate('ul');
-                       buttons.className = 'buttonList iconList';
-                       buttonGroupNavigation.appendChild(buttons);
-                       
-                       var listItem = elCreate('li');
-                       listItem.className = 'mediaCheckbox';
-                       buttons.appendChild(listItem);
-                       
-                       var a = elCreate('a');
-                       listItem.appendChild(a);
-                       
-                       var label = elCreate('label');
-                       a.appendChild(label);
-                       
-                       var checkbox = elCreate('input');
-                       checkbox.className = 'jsClipboardItem';
-                       elAttr(checkbox, 'type', 'checkbox');
-                       elData(checkbox, 'object-id', media.mediaID);
-                       label.appendChild(checkbox);
-                       
-                       if (Permission.get('admin.content.cms.canManageMedia')) {
-                               listItem = elCreate('li');
-                               listItem.className = 'jsMediaEditButton';
-                               elData(listItem, 'object-id', media.mediaID);
-                               buttons.appendChild(listItem);
-                               
-                               listItem.innerHTML = '<a><span class="icon icon16 fa-pencil jsTooltip" title="' + Language.get('wcf.global.button.edit') + '"></span> <span class="invisible">' + Language.get('wcf.global.button.edit') + '</span></a>';
-                               
-                               listItem = elCreate('li');
-                               listItem.className = 'jsDeleteButton';
-                               elData(listItem, 'object-id', media.mediaID);
-                               
-                               // use temporary title to not unescape html in filename
-                               var uuid = Core.getUuid();
-                               elData(listItem, 'confirm-message-html', StringUtil.unescapeHTML(Language.get('wcf.media.delete.confirmMessage', {
-                                       title: uuid
-                               })).replace(uuid, StringUtil.escapeHTML(media.filename)));
-                               buttons.appendChild(listItem);
-                               
-                               listItem.innerHTML = '<a><span class="icon icon16 fa-times jsTooltip" title="' + Language.get('wcf.global.button.delete') + '"></span> <span class="invisible">' + Language.get('wcf.global.button.delete') + '</span></a>';
-                       }
-               }
-       };
-       
-       return MediaManagerBase;
-});
-
-/**
- * Provides the media manager dialog for selecting media for Redactor editors.
- *
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Media/Manager/Editor
- */
-define('WoltLabSuite/Core/Media/Manager/Editor',['Core', 'Dictionary', 'Dom/Traverse', 'EventHandler', 'Language', 'Permission', 'Ui/Dialog', 'WoltLabSuite/Core/Controller/Clipboard', 'WoltLabSuite/Core/Media/Manager/Base'],
-       function(Core, Dictionary, DomTraverse, EventHandler, Language, Permission, UiDialog, ControllerClipboard, MediaManagerBase) {
-       "use strict";
-               
-               if (!COMPILER_TARGET_DEFAULT) {
-                       var Fake = function() {};
-                       Fake.prototype = {
-                               _addButtonEventListeners: function() {},
-                               _buildInsertDialog: function() {},
-                               _click: function() {},
-                               _getInsertDialogId: function() {},
-                               _getThumbnailSizes: function() {},
-                               _insertMedia: function() {},
-                               _insertMediaGallery: function() {},
-                               _insertMediaItem: function() {},
-                               _openInsertDialog: function() {},
-                               insertMedia: function() {},
-                               getMode: function() {},
-                               setupMediaElement: function() {},
-                               _dialogClose: function() {},
-                               _dialogInit: function() {},
-                               _dialogSetup: function() {},
-                               _dialogShow: function() {},
-                               _editMedia: function() {},
-                               _editorClose: function() {},
-                               _editorSuccess: function() {},
-                               _removeClipboardCheckboxes: function() {},
-                               _setMedia: function() {},
-                               addMedia: function() {},
-                               clipboardInsertMedia: function() {},
-                               getDialog: function() {},
-                               getOption: function() {},
-                               removeMedia: function() {},
-                               resetMedia: function() {},
-                               setMedia: function() {}
-                       };
-                       return Fake;
-               }
-       
-       /**
-        * @constructor
-        */
-       function MediaManagerEditor(options) {
-               options = Core.extend({
-                       callbackInsert: null
-               }, options);
-               
-               MediaManagerBase.call(this, options);
-               
-               this._forceClipboard = true;
-               this._activeButton = null;
-               var context = (this._options.editor) ? this._options.editor.core.toolbar()[0] : undefined;
-               this._buttons = elByClass(this._options.buttonClass || 'jsMediaEditorButton', context);
-               for (var i = 0, length = this._buttons.length; i < length; i++) {
-                       this._buttons[i].addEventListener(WCF_CLICK_EVENT, this._click.bind(this));
-               }
-               this._mediaToInsert = new Dictionary();
-               this._mediaToInsertByClipboard = false;
-               this._uploadData = null;
-               this._uploadId = null;
-               
-               if (this._options.editor && !this._options.editor.opts.woltlab.attachments) {
-                       var editorId = elData(this._options.editor.$editor[0], 'element-id');
-                       
-                       var uuid1 = EventHandler.add('com.woltlab.wcf.redactor2', 'dragAndDrop_' + editorId, this._editorUpload.bind(this));
-                       var uuid2 = EventHandler.add('com.woltlab.wcf.redactor2', 'pasteFromClipboard_' + editorId, this._editorUpload.bind(this));
-                       
-                       EventHandler.add('com.woltlab.wcf.redactor2', 'destory_' + editorId, function() {
-                               EventHandler.remove('com.woltlab.wcf.redactor2', 'dragAndDrop_' + editorId, uuid1);
-                               EventHandler.remove('com.woltlab.wcf.redactor2', 'dragAndDrop_' + editorId, uuid2);
-                       });
-                       
-                       EventHandler.add('com.woltlab.wcf.media.upload', 'success', this._mediaUploaded.bind(this));
-               }
-       }
-       Core.inherit(MediaManagerEditor, MediaManagerBase, {
-               /**
-                * @see WoltLabSuite/Core/Media/Manager/Base#_addButtonEventListeners
-                */
-               _addButtonEventListeners: function() {
-                       MediaManagerEditor._super.prototype._addButtonEventListeners.call(this);
-                       
-                       if (!this._mediaManagerMediaList) return;
-                       
-                       var listItems = DomTraverse.childrenByTag(this._mediaManagerMediaList, 'LI');
-                       for (var i = 0, length = listItems.length; i < length; i++) {
-                               var listItem = listItems[i];
-                               
-                               var insertIcon = elByClass('jsMediaInsertButton', listItem)[0];
-                               if (insertIcon) {
-                                       insertIcon.classList.remove('jsMediaInsertButton');
-                                       insertIcon.addEventListener(WCF_CLICK_EVENT, this._openInsertDialog.bind(this));
-                               }
-                       }
-               },
-               
-               /**
-                * Builds the dialog to setup inserting media files.
-                */
-               _buildInsertDialog: function() {
-                       var thumbnailOptions = '';
-                       
-                       var thumbnailSizes = this._getThumbnailSizes();
-                       for (var i = 0, length = thumbnailSizes.length; i < length; i++) {
-                               thumbnailOptions += '<option value="' + thumbnailSizes[i] + '">' + Language.get('wcf.media.insert.imageSize.' + thumbnailSizes[i]) + '</option>';
-                       }
-                       thumbnailOptions += '<option value="original">' + Language.get('wcf.media.insert.imageSize.original') + '</option>';
-                       
-                       var dialog = '<div class="section">'
-                       /*+ (this._mediaToInsert.size > 1 ? '<dl>'
-                               + '<dt>' + Language.get('wcf.media.insert.type') + '</dt>'
-                               + '<dd>'
-                                       + '<select name="insertType">'
-                                               + '<option value="separate">' + Language.get('wcf.media.insert.type.separate') + '</option>'
-                                               + '<option value="gallery">' + Language.get('wcf.media.insert.type.gallery') + '</option>'
-                                       + '</select>'
-                               + '</dd>'
-                       + '</dl>' : '')*/
-                       + '<dl class="thumbnailSizeSelection">'
-                               + '<dt>' + Language.get('wcf.media.insert.imageSize') + '</dt>'
-                               + '<dd>'
-                                       + '<select name="thumbnailSize">'
-                                               + thumbnailOptions
-                                       + '</select>'
-                               + '</dd>'
-                       + '</dl>'
-                       + '</div>'
-                       + '<div class="formSubmit">'
-                               + '<button class="buttonPrimary">' + Language.get('wcf.global.button.insert') + '</button>'
-                       + '</div>';
-                       
-                       UiDialog.open({
-                               _dialogSetup: (function() {
-                                       return {
-                                               id: this._getInsertDialogId(),
-                                               options: {
-                                                       onClose: this._editorClose.bind(this),
-                                                       onSetup: function(content) {
-                                                               elByClass('buttonPrimary', content)[0].addEventListener(WCF_CLICK_EVENT, this._insertMedia.bind(this));
-                                                               
-                                                               // toggle thumbnail size selection based on selected insert type
-                                                               /*var insertType = elBySel('select[name=insertType]', content);
-                                                               if (insertType !== null) {
-                                                                       var thumbnailSelection = elByClass('thumbnailSizeSelection', content)[0];
-                                                                       insertType.addEventListener('change', function(event) {
-                                                                               if (event.currentTarget.value === 'gallery') {
-                                                                                       elHide(thumbnailSelection);
-                                                                               }
-                                                                               else {
-                                                                                       elShow(thumbnailSelection);
-                                                                               }
-                                                                       });
-                                                               }*/
-                                                               var thumbnailSelection = elBySel('.thumbnailSizeSelection', content);
-                                                               elShow(thumbnailSelection);
-                                                       }.bind(this),
-                                                       title: Language.get('wcf.media.insert')
-                                               },
-                                               source: dialog
-                                       };
-                               }).bind(this)
-                       });
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Media/Manager/Base#_click
-                */
-               _click: function(event) {
-                       this._activeButton = event.currentTarget;
-                       
-                       MediaManagerEditor._super.prototype._click.call(this, event);
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Media/Manager/Base#_dialogShow
-                */
-               _dialogShow: function() {
-                       MediaManagerEditor._super.prototype._dialogShow.call(this);
-                       
-                       // check if data needs to be uploaded
-                       if (this._uploadData) {
-                               if (this._uploadData.file) {
-                                       this._upload.uploadFile(this._uploadData.file);
-                               }
-                               else {
-                                       this._uploadId = this._upload.uploadBlob(this._uploadData.blob);
-                               }
-                               
-                               this._uploadData = null;
-                       }
-               },
-               
-               /**
-                * Handles pasting and dragging and dropping files into the editor. 
-                * 
-                * @param       {object}        data    data of the uploaded file
-                */
-               _editorUpload: function(data) {
-                       this._uploadData = data;
-                       
-                       UiDialog.open(this);
-               },
-               
-               /**
-                * Returns the id of the insert dialog based on the media files to be inserted.
-                * 
-                * @return      {string}        insert dialog id
-                */
-               _getInsertDialogId: function() {
-                       var dialogId = 'mediaInsert';
-                       
-                       this._mediaToInsert.forEach(function(media, mediaId) {
-                               dialogId += '-' + mediaId;
-                       });
-                       
-                       return dialogId;
-               },
-               
-               /**
-                * Returns the supported thumbnail sizes (excluding `original`) for all media images to be inserted.
-                * 
-                * @return      {string[]}
-                */
-               _getThumbnailSizes: function() {
-                       var sizes = [];
-                       
-                       var supportedSizes = ['small', 'medium', 'large'];
-                       var size, supportSize;
-                       for (var i = 0, length = supportedSizes.length; i < length; i++) {
-                               size = supportedSizes[i];
-                               
-                               supportSize = true;
-                               this._mediaToInsert.forEach(function(media) {
-                                       if (!media[size + 'ThumbnailType']) {
-                                               supportSize = false;
-                                       }
-                               });
-                               
-                               if (supportSize) {
-                                       sizes.push(size);
-                               }
-                       }
-                       
-                       return sizes;
-               },
-               
-               /**
-                * Inserts media files into redactor.
-                * 
-                * @param       {Event?}        event
-                * @param       {string?}       thumbnailSize
-                * @param       {boolean?}      closeEditor
-                */
-               _insertMedia: function(event, thumbnailSize, closeEditor) {
-                       if (closeEditor === undefined) closeEditor = true;
-                       
-                       var insertType = 'separate';
-                       
-                       // update insert options with selected values if method is called by clicking on 'insert' button
-                       // in dialog
-                       if (event) {
-                               UiDialog.close(this._getInsertDialogId());
-                               
-                               var dialogContent = event.currentTarget.closest('.dialogContent');
-                               
-                               /*if (this._mediaToInsert.size > 1) {
-                                       insertType = elBySel('select[name=insertType]', dialogContent).value;
-                               }*/
-                               thumbnailSize = elBySel('select[name=thumbnailSize]', dialogContent).value;
-                       }
-                       
-                       if (this._options.callbackInsert !== null) {
-                               this._options.callbackInsert(this._mediaToInsert, insertType, thumbnailSize);
-                       }
-                       else {
-                               if (insertType === 'separate') {
-                                       this._options.editor.buffer.set();
-                                       
-                                       this._mediaToInsert.forEach(this._insertMediaItem.bind(this, thumbnailSize));
-                               }
-                               else {
-                                       this._insertMediaGallery();
-                               }
-                       }
-                       
-                       if (this._mediaToInsertByClipboard) {
-                               var mediaIds = [];
-                               this._mediaToInsert.forEach(function(media) {
-                                       mediaIds.push(media.mediaID);
-                               });
-                               
-                               ControllerClipboard.unmark('com.woltlab.wcf.media', mediaIds);
-                       }
-                       
-                       this._mediaToInsert = new Dictionary();
-                       this._mediaToInsertByClipboard = false;
-                       
-                       // close manager dialog
-                       if (closeEditor) {
-                               UiDialog.close(this);
-                       }
-               },
-               
-               /**
-                * Inserts a series of uploaded images using a slider.
-                * 
-                * @protected
-                */
-               _insertMediaGallery: function() {
-                       var mediaIds = [];
-                       this._mediaToInsert.forEach(function(item) {
-                               mediaIds.push(item.mediaID);
-                       });
-                       
-                       this._options.editor.buffer.set();
-                       this._options.editor.insert.text("[wsmg='" + mediaIds.join(',') + "'][/wsmg]");
-               },
-               
-               /**
-                * Inserts a single media item.
-                * 
-                * @param       {string}        thumbnailSize   preferred image dimension, is ignored for non-images
-                * @param       {Object}        item            media item data
-                * @protected
-                */
-               _insertMediaItem: function(thumbnailSize, item) {
-                       if (item.isImage) {
-                               var sizes = ['small', 'medium', 'large', 'original'];
-                               
-                               // check if size is actually available
-                               var available = '', size;
-                               for (var i = 0; i < 4; i++) {
-                                       size = sizes[i];
-                                       
-                                       if (item[size + 'ThumbnailHeight'] != 0) {
-                                               available = size;
-                                               
-                                               if (thumbnailSize == size) {
-                                                       break;
-                                               }
-                                       }
-                               }
-                               
-                               thumbnailSize = available;
-                               
-                               if (!thumbnailSize) thumbnailSize = 'original';
-                               
-                               var link = item.link;
-                               if (thumbnailSize !== 'original') {
-                                       link = item[thumbnailSize + 'ThumbnailLink'];
-                               }
-                               
-                               this._options.editor.insert.html('<img src="' + link + '" class="woltlabSuiteMedia" data-media-id="' + item.mediaID + '" data-media-size="' + thumbnailSize + '">');
-                       }
-                       else {
-                               this._options.editor.insert.text("[wsm='" + item.mediaID + "'][/wsm]");
-                       }
-               },
-               
-               /**
-                * Is called after media files are successfully uploaded to insert copied media.
-                * 
-                * @param       {object}        data            upload data
-                */
-               _mediaUploaded: function(data) {
-                       if (this._uploadId !== null && this._upload === data.upload) {
-                               if (this._uploadId === data.uploadId || (Array.isArray(this._uploadId) && this._uploadId.indexOf(data.uploadId) !== -1)) {
-                                       this._mediaToInsert = Dictionary.fromObject(data.media);
-                                       this._insertMedia(null, 'medium', false);
-                                       
-                                       this._uploadId = null;
-                               }
-                       }
-               },
-               
-               /**
-                * Handles clicking on the insert button.
-                * 
-                * @param       {Event}         event           insert button click event
-                */
-               _openInsertDialog: function(event) {
-                       this.insertMedia([~~elData(event.currentTarget, 'object-id')]);
-               },
-               
-               /**
-                * Is called to insert the media files with the given ids into an editor.
-                * 
-                * @param       {int[]}         mediaIds
-                */
-               clipboardInsertMedia: function(mediaIds) {
-                       this.insertMedia(mediaIds, true);
-               },
-               
-               /**
-                * Prepares insertion of the media files with the given ids.
-                * 
-                * @param       {array<int>}    mediaIds                ids of the media files to be inserted
-                * @param       {boolean?}      insertedByClipboard     is true if the media files are inserted by clipboard
-                */
-               insertMedia: function(mediaIds, insertedByClipboard) {
-                       this._mediaToInsert = new Dictionary();
-                       this._mediaToInsertByClipboard = insertedByClipboard || false;
-                       
-                       // open the insert dialog if all media files are images
-                       var imagesOnly = true, media;
-                       for (var i = 0, length = mediaIds.length; i < length; i++) {
-                               media = this._media.get(mediaIds[i]);
-                               this._mediaToInsert.set(media.mediaID, media);
-                               
-                               if (!media.isImage) {
-                                       imagesOnly = false;
-                               }
-                       }
-                       
-                       if (imagesOnly) {
-                               var thumbnailSizes = this._getThumbnailSizes();
-                               if (thumbnailSizes.length) {
-                                       UiDialog.close(this);
-                                       var dialogId = this._getInsertDialogId();
-                                       if (UiDialog.getDialog(dialogId)) {
-                                               UiDialog.openStatic(dialogId);
-                                       }
-                                       else {
-                                               this._buildInsertDialog();
-                                       }
-                               }
-                               else {
-                                       this._insertMedia(undefined, 'original');
-                               }
-                       }
-                       else {
-                               this._insertMedia();
-                       }
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Media/Manager/Base#getMode
-                */
-               getMode: function() {
-                       return 'editor';
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Media/Manager/Base#setupMediaElement
-                */
-               setupMediaElement: function(media, mediaElement) {
-                       MediaManagerEditor._super.prototype.setupMediaElement.call(this, media, mediaElement);
-                       
-                       // add media insertion icon
-                       var buttons = elBySel('nav.buttonGroupNavigation > ul', mediaElement);
-                       
-                       var listItem = elCreate('li');
-                       listItem.className = 'jsMediaInsertButton';
-                       elData(listItem, 'object-id', media.mediaID);
-                       buttons.appendChild(listItem);
-                       
-                       listItem.innerHTML = '<a><span class="icon icon16 fa-plus jsTooltip" title="' + Language.get('wcf.media.button.insert') + '"></span> <span class="invisible">' + Language.get('wcf.media.button.insert') + '</span></a>';
-               }
-       });
-       
-       return MediaManagerEditor;
-});
-
-/**
- * Provides the media manager dialog for selecting media for input elements.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Media/Manager/Select
- */
-define('WoltLabSuite/Core/Media/Manager/Select',['Core', 'Dom/Traverse', 'Dom/Util', 'Language', 'ObjectMap', 'Ui/Dialog', 'WoltLabSuite/Core/FileUtil', 'WoltLabSuite/Core/Media/Manager/Base'],
-       function(Core, DomTraverse, DomUtil, Language, ObjectMap, UiDialog, FileUtil, MediaManagerBase) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       _addButtonEventListeners: function() {},
-                       _chooseMedia: function() {},
-                       _click: function() {},
-                       getMode: function() {},
-                       setupMediaElement: function() {},
-                       _removeMedia: function() {},
-                       _clipboardAction: function() {},
-                       _dialogClose: function() {},
-                       _dialogInit: function() {},
-                       _dialogSetup: function() {},
-                       _dialogShow: function() {},
-                       _editMedia: function() {},
-                       _editorClose: function() {},
-                       _editorSuccess: function() {},
-                       _removeClipboardCheckboxes: function() {},
-                       _setMedia: function() {},
-                       addMedia: function() {},
-                       getDialog: function() {},
-                       getOption: function() {},
-                       removeMedia: function() {},
-                       resetMedia: function() {},
-                       setMedia: function() {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * @constructor
-        */
-       function MediaManagerSelect(options) {
-               MediaManagerBase.call(this, options);
-               
-               this._activeButton = null;
-               this._buttons = elByClass(this._options.buttonClass || 'jsMediaSelectButton');
-               this._storeElements = new ObjectMap();
-               
-               for (var i = 0, length = this._buttons.length; i < length; i++) {
-                       var button = this._buttons[i];
-                       
-                       // only consider buttons with a proper store specified
-                       var store = elData(button, 'store');
-                       if (store) {
-                               var storeElement = elById(store);
-                               if (storeElement && storeElement.tagName === 'INPUT') {
-                                       this._buttons[i].addEventListener(WCF_CLICK_EVENT, this._click.bind(this));
-                                       
-                                       this._storeElements.set(button, storeElement);
-                                       
-                                       // add remove button
-                                       var removeButton = elCreate('p');
-                                       removeButton.className = 'button';
-                                       DomUtil.insertAfter(removeButton, button);
-                                       
-                                       var icon = elCreate('span');
-                                       icon.className = 'icon icon16 fa-times';
-                                       removeButton.appendChild(icon);
-                                       
-                                       if (!storeElement.value) elHide(removeButton);
-                                       removeButton.addEventListener(WCF_CLICK_EVENT, this._removeMedia.bind(this));
-                               }
-                       }
-               }
-       }
-       Core.inherit(MediaManagerSelect, MediaManagerBase, {
-               /**
-                * @see WoltLabSuite/Core/Media/Manager/Base#_addButtonEventListeners
-                */
-               _addButtonEventListeners: function() {
-                       MediaManagerSelect._super.prototype._addButtonEventListeners.call(this);
-                       
-                       if (!this._mediaManagerMediaList) return;
-                       
-                       var listItems = DomTraverse.childrenByTag(this._mediaManagerMediaList, 'LI');
-                       for (var i = 0, length = listItems.length; i < length; i++) {
-                               var listItem = listItems[i];
-                               
-                               var chooseIcon = elByClass('jsMediaSelectButton', listItem)[0];
-                               if (chooseIcon) {
-                                       chooseIcon.classList.remove('jsMediaSelectButton');
-                                       chooseIcon.addEventListener(WCF_CLICK_EVENT, this._chooseMedia.bind(this));
-                               }
-                       }
-               },
-               
-               /**
-                * Handles clicking on a media choose icon.
-                * 
-                * @param       {Event}         event           click event
-                */
-               _chooseMedia: function(event) {
-                       if (this._activeButton === null) {
-                               throw new Error("Media cannot be chosen if no button is active.");
-                       }
-                       
-                       var media = this._media.get(~~elData(event.currentTarget, 'object-id'));
-                       
-                       // save selected media in store element
-                       elById(elData(this._activeButton, 'store')).value = media.mediaID;
-                       
-                       // display selected media
-                       var display = elData(this._activeButton, 'display');
-                       if (display) {
-                               var displayElement = elById(display);
-                               if (displayElement) {
-                                       if (media.isImage) {
-                                               displayElement.innerHTML = '<img src="' + (media.smallThumbnailLink ? media.smallThumbnailLink : media.link) + '" alt="' + (media.altText && media.altText[LANGUAGE_ID] ? media.altText[LANGUAGE_ID] : '') + '" />';
-                                       }
-                                       else {
-                                               var fileIcon = FileUtil.getIconNameByFilename(media.filename);
-                                               if (fileIcon) {
-                                                       fileIcon = '-' + fileIcon;
-                                               }
-                                               
-                                               displayElement.innerHTML = '<div class="box48" style="margin-bottom: 10px;">'
-                                                       + '<span class="icon icon48 fa-file' + fileIcon + '-o"></span>'
-                                                       + '<div class="containerHeadline">'
-                                                               + '<h3>' + media.filename + '</h3>'
-                                                               + '<p>' + media.formattedFilesize + '</p>'
-                                                       + '</div>'
-                                               + '</div>';
-                                       }
-                               }
-                       }
-                       
-                       // show remove button
-                       elShow(this._activeButton.nextElementSibling);
-                       
-                       UiDialog.close(this);
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Media/Manager/Base#_click
-                */
-               _click: function(event) {
-                       event.preventDefault();
-                       this._activeButton = event.currentTarget;
-                       
-                       MediaManagerSelect._super.prototype._click.call(this, event);
-                       
-                       if (!this._mediaManagerMediaList) return;
-                       
-                       var storeElement = this._storeElements.get(this._activeButton);
-                       var listItems = DomTraverse.childrenByTag(this._mediaManagerMediaList, 'LI'), listItem;
-                       for (var i = 0, length = listItems.length; i < length; i++) {
-                               listItem = listItems[i];
-                               if (storeElement.value && storeElement.value == elData(listItem, 'object-id')) {
-                                       listItem.classList.add('jsSelected');
-                               }
-                               else {
-                                       listItem.classList.remove('jsSelected');
-                               }
-                       }
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Media/Manager/Base#getMode
-                */
-               getMode: function() {
-                       return 'select';
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Media/Manager/Base#setupMediaElement
-                */
-               setupMediaElement: function(media, mediaElement) {
-                       MediaManagerSelect._super.prototype.setupMediaElement.call(this, media, mediaElement);
-                       
-                       // add media insertion icon
-                       var buttons = elBySel('nav.buttonGroupNavigation > ul', mediaElement);
-                       
-                       var listItem = elCreate('li');
-                       listItem.className = 'jsMediaSelectButton';
-                       elData(listItem, 'object-id', media.mediaID);
-                       buttons.appendChild(listItem);
-                       
-                       listItem.innerHTML = '<a><span class="icon icon16 fa-check jsTooltip" title="' + Language.get('wcf.media.button.select') + '"></span> <span class="invisible">' + Language.get('wcf.media.button.select') + '</span></a>';
-               },
-               
-               /**
-                * Handles clicking on the remove button.
-                *
-                * @param       {Event}         event           click event
-                */
-               _removeMedia: function(event) {
-                       event.preventDefault();
-                       
-                       var removeButton = event.currentTarget;
-                       elHide(removeButton);
-                       
-                       var button = removeButton.previousElementSibling;
-                       elById(elData(button, 'store')).value = 0;
-                       var display = elData(button, 'display');
-                       if (display) {
-                               var displayElement = elById(display);
-                               if (displayElement) {
-                                       displayElement.innerHTML = '';
-                               }
-                       }
-               }
-       });
-       
-       return MediaManagerSelect;
-});
-
-/**
- * Provides suggestions using an input field, designed to work with `wcf\data\ISearchAction`.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Search/Input
- */
-define('WoltLabSuite/Core/Ui/Search/Input',['Ajax', 'Core', 'EventKey', 'Dom/Util', 'Ui/SimpleDropdown'], function(Ajax, Core, EventKey, DomUtil, UiSimpleDropdown) {
-       "use strict";
-       
-       /**
-        * @param       {Element}       element         target input[type="text"]
-        * @param       {Object}        options         search options and settings
-        * @constructor
-        */
-       function UiSearchInput(element, options) { this.init(element, options); }
-       UiSearchInput.prototype = {
-               /**
-                * Initializes the search input field.
-                * 
-                * @param       {Element}       element         target input[type="text"]
-                * @param       {Object}        options         search options and settings
-                */
-               init: function(element, options) {
-                       this._element = element;
-                       if (!(this._element instanceof Element)) {
-                               throw new TypeError("Expected a valid DOM element.");
-                       }
-                       else if (this._element.nodeName !== 'INPUT' || (this._element.type !== 'search' && this._element.type !== 'text')) {
-                               throw new Error('Expected an input[type="text"].');
-                       }
-                       
-                       this._activeItem = null;
-                       this._dropdownContainerId = '';
-                       this._lastValue = '';
-                       this._list = null;
-                       this._request = null;
-                       this._timerDelay = null;
-                       
-                       this._options = Core.extend({
-                               ajax: {
-                                       actionName: 'getSearchResultList',
-                                       className: '',
-                                       interfaceName: 'wcf\\data\\ISearchAction'
-                               },
-                               callbackDropdownInit: null,
-                               callbackSelect: null,
-                               delay: 500,
-                               excludedSearchValues: [],
-                               minLength: 3,
-                               noResultPlaceholder: '',
-                               preventSubmit: false
-                       }, options);
-                       
-                       // disable auto-complete as it collides with the suggestion dropdown
-                       elAttr(this._element, 'autocomplete', 'off');
-                       
-                       this._element.addEventListener('keydown', this._keydown.bind(this));
-                       this._element.addEventListener('keyup', this._keyup.bind(this));
-               },
-               
-               /**
-                * Adds an excluded search value.
-                * 
-                * @param       {string}        value   excluded value
-                */
-               addExcludedSearchValues: function (value) {
-                       if (this._options.excludedSearchValues.indexOf(value) === -1) {
-                               this._options.excludedSearchValues.push(value);
-                       }
-               },
-               
-               /**
-                * Removes a value from the excluded search values.
-                * 
-                * @param       {string}        value   excluded value
-                */
-               removeExcludedSearchValues: function (value) {
-                       var index = this._options.excludedSearchValues.indexOf(value);
-                       if (index !== -1) {
-                               this._options.excludedSearchValues.splice(index, 1);
-                       }
-               },
-               
-               /**
-                * Handles the 'keydown' event.
-                * 
-                * @param       {Event}         event           event object
-                * @protected
-                */
-               _keydown: function(event) {
-                       if ((this._activeItem !== null && UiSimpleDropdown.isOpen(this._dropdownContainerId)) || this._options.preventSubmit) {
-                               if (EventKey.Enter(event)) {
-                                       event.preventDefault();
-                               }
-                       }
-                       
-                       if (EventKey.ArrowUp(event) || EventKey.ArrowDown(event) || EventKey.Escape(event)) {
-                               event.preventDefault();
-                       }
-               },
-               
-               /**
-                * Handles the 'keyup' event, provides keyboard navigation and executes search queries.
-                * 
-                * @param       {Event}         event           event object
-                * @protected
-                */
-               _keyup: function(event) {
-                       // handle dropdown keyboard navigation
-                       if (this._activeItem !== null) {
-                               if (UiSimpleDropdown.isOpen(this._dropdownContainerId)) {
-                                       if (EventKey.ArrowUp(event)) {
-                                               event.preventDefault();
-                                               
-                                               return this._keyboardPreviousItem();
-                                       }
-                                       else if (EventKey.ArrowDown(event)) {
-                                               event.preventDefault();
-                                               
-                                               return this._keyboardNextItem();
-                                       }
-                                       else if (EventKey.Enter(event)) {
-                                               event.preventDefault();
-                                               
-                                               return this._keyboardSelectItem();
-                                       }
-                               }
-                               else {
-                                       this._activeItem = null;
-                               }
-                       }
-                       
-                       // close list on escape
-                       if (EventKey.Escape(event)) {
-                               UiSimpleDropdown.close(this._dropdownContainerId);
-                               
-                               return;
-                       }
-                       
-                       var value = this._element.value.trim();
-                       if (this._lastValue === value) {
-                               // value did not change, e.g. previously it was "Test" and now it is "Test ",
-                               // but the trailing whitespace has been ignored
-                               return;
-                       }
-                       
-                       this._lastValue = value;
-                       
-                       if (value.length < this._options.minLength) {
-                               if (this._dropdownContainerId) {
-                                       UiSimpleDropdown.close(this._dropdownContainerId);
-                                       this._activeItem = null;
-                               }
-                               
-                               // value below threshold
-                               return;
-                       }
-                       
-                       if (this._options.delay) {
-                               if (this._timerDelay !== null) {
-                                       window.clearTimeout(this._timerDelay);
-                               }
-                               
-                               this._timerDelay = window.setTimeout((function() {
-                                       this._search(value);
-                               }).bind(this), this._options.delay);
-                       }
-                       else {
-                               this._search(value);
-                       }
-               },
-               
-               /**
-                * Queries the server with the provided search string.
-                * 
-                * @param       {string}        value   search string
-                * @protected
-                */
-               _search: function(value) {
-                       if (this._request) {
-                               this._request.abortPrevious();
-                       }
-                       
-                       this._request = Ajax.api(this, this._getParameters(value));
-               },
-               
-               /**
-                * Returns additional AJAX parameters.
-                * 
-                * @param       {string}        value   search string
-                * @return      {Object}        additional AJAX parameters
-                * @protected
-                */
-               _getParameters: function(value) {
-                       return {
-                               parameters: {
-                                       data: {
-                                               excludedSearchValues: this._options.excludedSearchValues,
-                                               searchString: value
-                                       }
-                               }
-                       };
-               },
-               
-               /**
-                * Selects the next dropdown item.
-                * 
-                * @protected
-                */
-               _keyboardNextItem: function() {
-                       this._activeItem.classList.remove('active');
-                       
-                       if (this._activeItem.nextElementSibling) {
-                               this._activeItem = this._activeItem.nextElementSibling;
-                       }
-                       else {
-                               this._activeItem = this._list.children[0];
-                       }
-                       
-                       this._activeItem.classList.add('active');
-               },
-               
-               /**
-                * Selects the previous dropdown item.
-                * 
-                * @protected
-                */
-               _keyboardPreviousItem: function() {
-                       this._activeItem.classList.remove('active');
-                       
-                       if (this._activeItem.previousElementSibling) {
-                               this._activeItem = this._activeItem.previousElementSibling;
-                       }
-                       else {
-                               this._activeItem = this._list.children[this._list.childElementCount - 1];
-                       }
-                       
-                       this._activeItem.classList.add('active');
-               },
-               
-               /**
-                * Selects the active item from the dropdown.
-                * 
-                * @protected
-                */
-               _keyboardSelectItem: function() {
-                       this._selectItem(this._activeItem);
-               },
-               
-               /**
-                * Selects an item from the dropdown by clicking it.
-                * 
-                * @param       {Event}         event           event object
-                * @protected
-                */
-               _clickSelectItem: function(event) {
-                       this._selectItem(event.currentTarget);
-               },
-               
-               /**
-                * Selects an item.
-                * 
-                * @param       {Element}       item    selected item
-                * @protected
-                */
-               _selectItem: function(item) {
-                       if (this._options.callbackSelect && this._options.callbackSelect(item) === false) {
-                               this._element.value = '';
-                       }
-                       else {
-                               this._element.value = elData(item, 'label');
-                       }
-                       
-                       this._activeItem = null;
-                       UiSimpleDropdown.close(this._dropdownContainerId);
-               },
-               
-               /**
-                * Handles successful AJAX requests.
-                * 
-                * @param       {Object}        data    response data
-                * @protected
-                */
-               _ajaxSuccess: function(data) {
-                       var createdList = false;
-                       if (this._list === null) {
-                               this._list = elCreate('ul');
-                               this._list.className = 'dropdownMenu';
-                               
-                               createdList = true;
-                               
-                               if (typeof this._options.callbackDropdownInit === 'function') {
-                                       this._options.callbackDropdownInit(this._list);
-                               }
-                       }
-                       else {
-                               // reset current list
-                               this._list.innerHTML = '';
-                       }
-                       
-                       if (typeof data.returnValues === 'object') {
-                               var callbackClick = this._clickSelectItem.bind(this), listItem;
-                               
-                               for (var key in data.returnValues) {
-                                       if (data.returnValues.hasOwnProperty(key)) {
-                                               listItem = this._createListItem(data.returnValues[key]);
-                                               
-                                               listItem.addEventListener(WCF_CLICK_EVENT, callbackClick);
-                                               this._list.appendChild(listItem);
-                                       }
-                               }
-                       }
-                       
-                       if (createdList) {
-                               DomUtil.insertAfter(this._list, this._element);
-                               UiSimpleDropdown.initFragment(this._element.parentNode, this._list);
-                               
-                               this._dropdownContainerId = DomUtil.identify(this._element.parentNode);
-                       }
-                       
-                       if (this._dropdownContainerId) {
-                               this._activeItem = null;
-                               
-                               if (!this._list.childElementCount && this._handleEmptyResult() === false) {
-                                       UiSimpleDropdown.close(this._dropdownContainerId);
-                               }
-                               else {
-                                       UiSimpleDropdown.open(this._dropdownContainerId, true);
-                                       
-                                       // mark first item as active
-                                       if (this._list.childElementCount && ~~elData(this._list.children[0], 'object-id')) {
-                                               this._activeItem = this._list.children[0];
-                                               this._activeItem.classList.add('active');
-                                       }
-                               }
-                       }
-               },
-               
-               /**
-                * Handles an empty result set, return a boolean false to hide the dropdown.
-                * 
-                * @return      {boolean}      false to close the dropdown
-                * @protected
-                */
-               _handleEmptyResult: function() {
-                       if (!this._options.noResultPlaceholder) {
-                               return false;
-                       }
-                       
-                       var listItem = elCreate('li');
-                       listItem.className = 'dropdownText';
-                       
-                       var span = elCreate('span');
-                       span.textContent = this._options.noResultPlaceholder;
-                       listItem.appendChild(span);
-                       
-                       this._list.appendChild(listItem);
-                       
-                       return true;
-               },
-               
-               /**
-                * Creates an list item from response data.
-                * 
-                * @param       {Object}        item    response data
-                * @return      {Element}       list item
-                * @protected
-                */
-               _createListItem: function(item) {
-                       var listItem = elCreate('li');
-                       elData(listItem, 'object-id', item.objectID);
-                       elData(listItem, 'label', item.label);
-                       
-                       var span = elCreate('span');
-                       span.textContent = item.label;
-                       listItem.appendChild(span);
-                       
-                       return listItem;
-               },
-               
-               _ajaxSetup: function() {
-                       return {
-                               data: this._options.ajax
-                       };
-               }
-       };
-       
-       return UiSearchInput;
-});
-
-/**
- * Provides suggestions for users, optionally supporting groups.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/User/Search/Input
- * @see         module:WoltLabSuite/Core/Ui/Search/Input
- */
-define('WoltLabSuite/Core/Ui/User/Search/Input',['Core', 'WoltLabSuite/Core/Ui/Search/Input'], function(Core, UiSearchInput) {
-       "use strict";
-       
-       /**
-        * @param       {Element}       element         input element
-        * @param       {Object=}       options         search options and settings
-        * @constructor
-        */
-       function UiUserSearchInput(element, options) { this.init(element, options); }
-       Core.inherit(UiUserSearchInput, UiSearchInput, {
-               init: function(element, options) {
-                       var includeUserGroups = (Core.isPlainObject(options) && options.includeUserGroups === true);
-                       
-                       options = Core.extend({
-                               ajax: {
-                                       className: 'wcf\\data\\user\\UserAction',
-                                       parameters: {
-                                               data: {
-                                                       includeUserGroups: (includeUserGroups ? 1 : 0)
-                                               }
-                                       }
-                               }
-                       }, options);
-                       
-                       UiUserSearchInput._super.prototype.init.call(this, element, options);
-               },
-               
-               _createListItem: function(item) {
-                       var listItem = UiUserSearchInput._super.prototype._createListItem.call(this, item);
-                       elData(listItem, 'type', item.type);
-                       
-                       var box = elCreate('div');
-                       box.className = 'box16';
-                       box.innerHTML = (item.type === 'group') ? '<span class="icon icon16 fa-users"></span>' : item.icon;
-                       box.appendChild(listItem.children[0]);
-                       listItem.appendChild(box);
-                       
-                       return listItem;
-               }
-       });
-       
-       return UiUserSearchInput;
-});
-
-define('WoltLabSuite/Core/Ui/Acl/Simple',['Language', 'StringUtil', 'Dom/ChangeListener', 'WoltLabSuite/Core/Ui/User/Search/Input'], function(Language, StringUtil, DomChangeListener, UiUserSearchInput) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _build: function() {},
-                       _select: function() {},
-                       _removeItem: function() {}
-               };
-               return Fake;
-       }
-       
-       function UiAclSimple(prefix, inputName) { this.init(prefix, inputName); }
-       UiAclSimple.prototype = {
-               init: function(prefix, inputName) {
-                       this._prefix = prefix || '';
-                       this._inputName = inputName || 'aclValues';
-                       
-                       this._build();
-               },
-               
-               _build: function () {
-                       var container = elById(this._prefix + 'aclInputContainer');
-                       
-                       elById(this._prefix + 'aclAllowAll').addEventListener('change', (function() {
-                               elHide(container);
-                       }));
-                       elById(this._prefix + 'aclAllowAll_no').addEventListener('change', (function() {
-                               elShow(container);
-                       }));
-                       
-                       this._list = elById(this._prefix + 'aclAccessList');
-                       this._list.addEventListener(WCF_CLICK_EVENT, this._removeItem.bind(this));
-                       
-                       var excludedSearchValues = [];
-                       elBySelAll('.aclLabel', this._list, function(label) {
-                               excludedSearchValues.push(label.textContent);
-                       });
-                       
-                       this._searchInput = new UiUserSearchInput(elById(this._prefix + 'aclSearchInput'), {
-                               callbackSelect: this._select.bind(this),
-                               includeUserGroups: true,
-                               excludedSearchValues: excludedSearchValues,
-                               preventSubmit: true,
-                       });
-                       
-                       this._aclListContainer = elById(this._prefix + 'aclListContainer');
-                       
-                       DomChangeListener.trigger();
-               },
-               
-               _select: function(listItem) {
-                       var type = elData(listItem, 'type');
-                       var label = elData(listItem, 'label');
-                       
-                       var html = '<span class="icon icon16 fa-' + (type === 'group' ? 'users' : 'user') + '"></span>';
-                       html += '<span class="aclLabel">' + StringUtil.escapeHTML(label) + '</span>';
-                       html += '<span class="icon icon16 fa-times pointer jsTooltip" title="' + Language.get('wcf.global.button.delete') + '"></span>';
-                       html += '<input type="hidden" name="' + this._inputName + '[' + type + '][]" value="' + elData(listItem, 'object-id') + '">';
-                       
-                       var item = elCreate('li');
-                       item.innerHTML = html;
-                       
-                       var firstUser = elBySel('.fa-user', this._list);
-                       if (firstUser === null) {
-                               this._list.appendChild(item);
-                       }
-                       else {
-                               this._list.insertBefore(item, firstUser.parentNode);
-                       }
-                       
-                       elShow(this._aclListContainer);
-                       
-                       this._searchInput.addExcludedSearchValues(label);
-                       
-                       DomChangeListener.trigger();
-                       
-                       return false;
-               },
-               
-               _removeItem: function (event) {
-                       if (event.target.classList.contains('fa-times')) {
-                               var label = elBySel('.aclLabel', event.target.parentNode);
-                               this._searchInput.removeExcludedSearchValues(label.textContent);
-                               
-                               elRemove(event.target.parentNode);
-                               
-                               if (this._list.childElementCount === 0) {
-                                       elHide(this._aclListContainer);
-                               }
-                       }
-               }
-       };
-       
-       return UiAclSimple;
-});
-
-/**
- * Handles the 'mark as read' action for articles.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Article/MarkAllAsRead
- */
-define('WoltLabSuite/Core/Ui/Article/MarkAllAsRead',['Ajax'], function(Ajax) {
-       "use strict";
-       
-       return {
-               init: function() {
-                       elBySelAll('.markAllAsReadButton', undefined, (function(button) {
-                               button.addEventListener(WCF_CLICK_EVENT, this._click.bind(this));
-                       }).bind(this));
-               },
-               
-               _click: function(event) {
-                       event.preventDefault();
-                       
-                       Ajax.api(this);
-               },
-               
-               _ajaxSuccess: function() {
-                       /* remove obsolete badges */
-                       // main menu
-                       var badge = elBySel('.mainMenu .active .badge');
-                       if (badge) elRemove(badge);
-                       
-                       // article list
-                       elBySelAll('.articleList .newMessageBadge', undefined, elRemove);
-               },
-               
-               _ajaxSetup: function() {
-                       return {
-                               data: {
-                                       actionName: 'markAllAsRead',
-                                       className: 'wcf\\data\\article\\ArticleAction'
-                               }
-                       };
-               }
-       };
-});
-
-define('WoltLabSuite/Core/Ui/Article/Search',['Ajax', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog'], function(Ajax, EventKey, Language, StringUtil, DomUtil, UiDialog) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       open: function() {},
-                       _search: function() {},
-                       _click: function() {},
-                       _ajaxSuccess: function() {},
-                       _ajaxSetup: function() {},
-                       _dialogSetup: function() {}
-               };
-               return Fake;
-       }
-       
-       var _callbackSelect, _resultContainer, _resultList, _searchInput = null;
-       
-       return {
-               open: function(callbackSelect) {
-                       _callbackSelect = callbackSelect;
-                       
-                       UiDialog.open(this);
-               },
-               
-               _search: function (event) {
-                       event.preventDefault();
-                       
-                       var inputContainer = _searchInput.parentNode;
-                       
-                       var value = _searchInput.value.trim();
-                       if (value.length < 3) {
-                               elInnerError(inputContainer, Language.get('wcf.article.search.error.tooShort'));
-                               return;
-                       }
-                       else {
-                               elInnerError(inputContainer, false);
-                       }
-                       
-                       Ajax.api(this, {
-                               parameters: {
-                                       searchString: value
-                               }
-                       });
-               },
-               
-               _click: function (event) {
-                       event.preventDefault();
-                       
-                       _callbackSelect(elData(event.currentTarget, 'article-id'));
-                       
-                       UiDialog.close(this);
-               },
-               
-               _ajaxSuccess: function(data) {
-                       var html = '', article;
-                       //noinspection JSUnresolvedVariable
-                       for (var i = 0, length = data.returnValues.length; i < length; i++) {
-                               //noinspection JSUnresolvedVariable
-                               article = data.returnValues[i];
-                               
-                               html += '<li>'
-                                               + '<div class="containerHeadline pointer" data-article-id="' + article.articleID + '">'
-                                                       + '<h3>' + StringUtil.escapeHTML(article.name) + '</h3>'
-                                                       + '<small>' + StringUtil.escapeHTML(article.displayLink) + '</small>'
-                                               + '</div>'
-                                       + '</li>';
-                       }
-                       
-                       _resultList.innerHTML = html;
-                       
-                       window[html ? 'elShow' : 'elHide'](_resultContainer);
-                       
-                       if (html) {
-                               elBySelAll('.containerHeadline', _resultList, (function(item) {
-                                       item.addEventListener(WCF_CLICK_EVENT, this._click.bind(this));
-                               }).bind(this));
-                       }
-                       else {
-                               elInnerError(_searchInput.parentNode, Language.get('wcf.article.search.error.noResults'));
-                       }
-               },
-               
-               _ajaxSetup: function () {
-                       return {
-                               data: {
-                                       actionName: 'search',
-                                       className: 'wcf\\data\\article\\ArticleAction'
-                               }
-                       };
-               },
-               
-               _dialogSetup: function() {
-                       return {
-                               id: 'wcfUiArticleSearch',
-                               options: {
-                                       onSetup: (function() {
-                                               var callbackSearch = this._search.bind(this);
-                                               
-                                               _searchInput = elById('wcfUiArticleSearchInput');
-                                               _searchInput.addEventListener('keydown', function(event) {
-                                                       if (EventKey.Enter(event)) {
-                                                               callbackSearch(event);
-                                                       }
-                                               });
-                                               
-                                               _searchInput.nextElementSibling.addEventListener(WCF_CLICK_EVENT, callbackSearch);
-                                               
-                                               _resultContainer = elById('wcfUiArticleSearchResultContainer');
-                                               _resultList = elById('wcfUiArticleSearchResultList');
-                                       }).bind(this),
-                                       onShow: function() {
-                                               _searchInput.focus();
-                                       },
-                                       title: Language.get('wcf.article.search')
-                               },
-                               source: '<div class="section">'
-                                       + '<dl>'
-                                               + '<dt><label for="wcfUiArticleSearchInput">' + Language.get('wcf.article.search.name') + '</label></dt>'
-                                               + '<dd>'
-                                                       + '<div class="inputAddon">'
-                                                               + '<input type="text" id="wcfUiArticleSearchInput" class="long">'
-                                                               + '<a href="#" class="inputSuffix"><span class="icon icon16 fa-search"></span></a>'
-                                                       + '</div>'
-                                               + '</dd>'
-                                       + '</dl>'
-                               + '</div>'
-                               + '<section id="wcfUiArticleSearchResultContainer" class="section" style="display: none;">'
-                                       + '<header class="sectionHeader">'
-                                               + '<h2 class="sectionTitle">' + Language.get('wcf.article.search.results') + '</h2>'
-                                       + '</header>'
-                                       + '<ol id="wcfUiArticleSearchResultList" class="containerList"></ol>'
-                               + '</section>'
-                       };
-               }
-       };
-});
-
-/**
- * Wrapper class to provide color picker support. Constructing a new object does not
- * guarantee the picker to be ready at the time of call.
- * 
- * @author      Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module      WoltLabSuite/Core/Ui/Color/Picker
- */
-define('WoltLabSuite/Core/Ui/Color/Picker',['Core'], function (Core) {
-       "use strict";
-       
-       var _marshal = function (element, options) {
-               if (typeof window.WCF === 'object' && typeof window.WCF.ColorPicker === 'function') {
-                       _marshal = function (element, options) {
-                               var picker = new window.WCF.ColorPicker(element);
-                               
-                               if (typeof options.callbackSubmit === 'function') {
-                                       picker.setCallbackSubmit(options.callbackSubmit);
-                               }
-                               
-                               return picker;
-                       };
-                       
-                       return _marshal(element, options);
-               }
-               else {
-                       if (_queue.length === 0) {
-                               window.__wcf_bc_colorPickerInit = function () {
-                                       _queue.forEach(function (data) {
-                                               _marshal(data[0], data[1]);
-                                       });
-                                       
-                                       window.__wcf_bc_colorPickerInit = undefined;
-                                       _queue = [];
-                               };
-                       }
-                       
-                       _queue.push([element, options]);
-               }
-       };
-       var _queue = [];
-       
-       /**
-        * @constructor
-        */
-       function UiColorPicker(element, options) { this.init(element, options); }
-       UiColorPicker.prototype = {
-               /**
-                * Initializes a new color picker instance. This is actually just a wrapper that does
-                * not guarantee the picker to be ready at the time of call.
-                * 
-                * @param       {Element}       element         input element
-                * @param       {Object}        options         list of initialization options
-                */
-               init: function (element, options) {
-                       if (!(element instanceof Element)) {
-                               throw new TypeError("Expected a valid DOM element, use `UiColorPicker.fromSelector()` if you want to use a CSS selector.");
-                       }
-                       
-                       this._options = Core.extend({
-                               callbackSubmit: null
-                       }, options);
-                       
-                       _marshal(element, options);
-               }
-       };
-       
-       /**
-        * Initializes a color picker for all input elements matching the given selector.
-        * 
-        * @param       {string}        selector        CSS selector
-        */
-       UiColorPicker.fromSelector = function (selector) {
-               elBySelAll(selector, undefined, function (element) {
-                       new UiColorPicker(element);
-               });
-       };
-       
-       return UiColorPicker;
-});
-
-/**
- * Handles the comment add feature.
- * 
- * Warning: This implementation is also used for responses, but in a slightly
- *          modified version. Changes made to this class need to be verified
- *          against the response implementation.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Comment/Add
- */
-define('WoltLabSuite/Core/Ui/Comment/Add',[
-       'Ajax', 'Core', 'EventHandler', 'Language', 'Dom/ChangeListener', 'Dom/Util', 'Dom/Traverse', 'Ui/Dialog', 'Ui/Notification', 'WoltLabSuite/Core/Ui/Scroll', 'EventKey', 'User', 'WoltLabSuite/Core/Controller/Captcha'
-],
-function(
-       Ajax, Core, EventHandler, Language, DomChangeListener, DomUtil, DomTraverse, UiDialog, UiNotification, UiScroll, EventKey, User, ControllerCaptcha
-) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _submitGuestDialog: function() {},
-                       _submit: function() {},
-                       _getParameters: function () {},
-                       _validate: function() {},
-                       throwError: function() {},
-                       _showLoadingOverlay: function() {},
-                       _hideLoadingOverlay: function() {},
-                       _reset: function() {},
-                       _handleError: function() {},
-                       _getEditor: function() {},
-                       _insertMessage: function() {},
-                       _ajaxSuccess: function() {},
-                       _ajaxFailure: function() {},
-                       _ajaxSetup: function() {},
-                       _cancelGuestDialog: function() {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * @constructor
-        */
-       function UiCommentAdd(container) { this.init(container); }
-       UiCommentAdd.prototype = {
-               /**
-                * Initializes a new quick reply field.
-                * 
-                * @param       {Element}       container       container element
-                */
-               init: function(container) {
-                       this._container = container;
-                       this._content = elBySel('.jsOuterEditorContainer', this._container);
-                       this._textarea = elBySel('.wysiwygTextarea', this._container);
-                       this._editor = null;
-                       this._loadingOverlay = null;
-                       
-                       this._content.addEventListener(WCF_CLICK_EVENT, (function (event) {
-                               if (this._content.classList.contains('collapsed')) {
-                                       event.preventDefault();
-                                       
-                                       this._content.classList.remove('collapsed');
-                                       
-                                       this._focusEditor();
-                               }
-                       }).bind(this));
-                       
-                       // handle submit button
-                       var submitButton = elBySel('button[data-type="save"]', this._container);
-                       submitButton.addEventListener(WCF_CLICK_EVENT, this._submit.bind(this));
-               },
-               
-               /**
-                * Scrolls the editor into view and sets the caret to the end of the editor.
-                * 
-                * @protected
-                */
-               _focusEditor: function () {
-                       UiScroll.element(this._container, (function () {
-                               window.jQuery(this._textarea).redactor('WoltLabCaret.endOfEditor');
-                       }).bind(this));
-               },
-               
-               /**
-                * Submits the guest dialog.
-                * 
-                * @param       {Event}         event
-                * @protected
-                */
-               _submitGuestDialog: function(event) {
-                       // only submit when enter key is pressed
-                       if (event.type === 'keypress' && !EventKey.Enter(event)) {
-                               return;
-                       }
-                       
-                       var usernameInput = elBySel('input[name=username]', event.currentTarget.closest('.dialogContent'));
-                       if (usernameInput.value === '') {
-                               elInnerError(usernameInput, Language.get('wcf.global.form.error.empty'));
-                               usernameInput.closest('dl').classList.add('formError');
-                               
-                               return;
-                       }
-                       
-                       var parameters = {
-                               parameters: {
-                                       data: {
-                                               username: usernameInput.value
-                                       }
-                               }
-                       };
-                       
-                       if (ControllerCaptcha.has('commentAdd')) {
-                               var data = ControllerCaptcha.getData('commentAdd');
-                               if (data instanceof Promise) {
-                                       data.then((function (data) {
-                                               parameters = Core.extend(parameters, data);
-                                               this._submit(undefined, parameters);
-                                       }).bind(this));
-                               }
-                               else {
-                                       parameters = Core.extend(parameters, data);
-                                       this._submit(undefined, parameters);
-                               }
-                       }
-                       else {
-                               this._submit(undefined, parameters);
-                       }
-               },
-               
-               /**
-                * Validates the message and submits it to the server.
-                * 
-                * @param       {Event?}        event                   event object
-                * @param       {Object?}       additionalParameters    additional parameters sent to the server
-                * @protected
-                */
-               _submit: function(event, additionalParameters) {
-                       if (event) {
-                               event.preventDefault();
-                       }
-                       
-                       if (!this._validate()) {
-                               // validation failed, bail out
-                               return;
-                       }
-                       
-                       this._showLoadingOverlay();
-                       
-                       // build parameters
-                       var parameters = this._getParameters();
-                       
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'submit_text', parameters.data);
-                       
-                       if (!User.userId && !additionalParameters) {
-                               parameters.requireGuestDialog = true;
-                       }
-                       
-                       Ajax.api(this, Core.extend({
-                               parameters: parameters
-                       }, additionalParameters));
-               },
-               
-               /**
-                * Returns the request parameters to add a comment.
-                * 
-                * @return      {{data: {message: string, objectID: number, objectTypeID: number}}}
-                * @protected
-                */
-               _getParameters: function () {
-                       var commentList = this._container.closest('.commentList');
-                       
-                       return {
-                               data: {
-                                       message: this._getEditor().code.get(),
-                                       objectID: ~~elData(commentList, 'object-id'),
-                                       objectTypeID: ~~elData(commentList, 'object-type-id')
-                               }
-                       };
-               },
-               
-               /**
-                * Validates the message and invokes listeners to perform additional validation.
-                * 
-                * @return      {boolean}       validation result
-                * @protected
-                */
-               _validate: function() {
-                       // remove all existing error elements
-                       elBySelAll('.innerError', this._container, elRemove);
-                       
-                       // check if editor contains actual content
-                       if (this._getEditor().utils.isEmpty()) {
-                               this.throwError(this._textarea, Language.get('wcf.global.form.error.empty'));
-                               return false;
-                       }
-                       
-                       var data = {
-                               api: this,
-                               editor: this._getEditor(),
-                               message: this._getEditor().code.get(),
-                               valid: true
-                       };
-                       
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'validate_text', data);
-                       
-                       return (data.valid !== false);
-               },
-               
-               /**
-                * Throws an error by adding an inline error to target element.
-                * 
-                * @param       {Element}       element         erroneous element
-                * @param       {string}        message         error message
-                */
-               throwError: function(element, message) {
-                       elInnerError(element, (message === 'empty' ? Language.get('wcf.global.form.error.empty') : message));
-               },
-               
-               /**
-                * Displays a loading spinner while the request is processed by the server.
-                * 
-                * @protected
-                */
-               _showLoadingOverlay: function() {
-                       if (this._loadingOverlay === null) {
-                               this._loadingOverlay = elCreate('div');
-                               this._loadingOverlay.className = 'commentLoadingOverlay';
-                               this._loadingOverlay.innerHTML = '<span class="icon icon96 fa-spinner"></span>';
-                       }
-                       
-                       this._content.classList.add('loading');
-                       this._content.appendChild(this._loadingOverlay);
-               },
-               
-               /**
-                * Hides the loading spinner.
-                * 
-                * @protected
-                */
-               _hideLoadingOverlay: function() {
-                       this._content.classList.remove('loading');
-                       
-                       var loadingOverlay = elBySel('.commentLoadingOverlay', this._content);
-                       if (loadingOverlay !== null) {
-                               loadingOverlay.parentNode.removeChild(loadingOverlay);
-                       }
-               },
-               
-               /**
-                * Resets the editor contents and notifies event listeners.
-                * 
-                * @protected
-                */
-               _reset: function() {
-                       this._getEditor().code.set('<p>\u200b</p>');
-                       
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'reset_text');
-                       
-                       if (document.activeElement) {
-                               document.activeElement.blur();
-                       }
-                       
-                       this._content.classList.add('collapsed');
-               },
-               
-               /**
-                * Handles errors occurred during server processing.
-                * 
-                * @param       {Object}        data    response data
-                * @protected
-                */
-               _handleError: function(data) {
-                       //noinspection JSUnresolvedVariable
-                       this.throwError(this._textarea, data.returnValues.errorType);
-               },
-               
-               /**
-                * Returns the current editor instance.
-                * 
-                * @return      {Object}       editor instance
-                * @protected
-                */
-               _getEditor: function() {
-                       if (this._editor === null) {
-                               if (typeof window.jQuery === 'function') {
-                                       this._editor = window.jQuery(this._textarea).data('redactor');
-                               }
-                               else {
-                                       throw new Error("Unable to access editor, jQuery has not been loaded yet.");
-                               }
-                       }
-                       
-                       return this._editor;
-               },
-               
-               /**
-                * Inserts the rendered message.
-                * 
-                * @param       {Object}        data    response data
-                * @return      {Element}       scroll target
-                * @protected
-                */
-               _insertMessage: function(data) {
-                       // insert HTML
-                       //noinspection JSCheckFunctionSignatures
-                       DomUtil.insertHtml(data.returnValues.template, this._container, 'after');
-                       
-                       UiNotification.show(Language.get('wcf.global.success.add'));
-                       
-                       DomChangeListener.trigger();
-                       
-                       return this._container.nextElementSibling;
-               },
-               
-               /**
-                * @param {{returnValues:{guestDialog:string}}} data
-                * @protected
-                */
-               _ajaxSuccess: function(data) {
-                       if (!User.userId && data.returnValues.guestDialog) {
-                               UiDialog.openStatic('jsDialogGuestComment', data.returnValues.guestDialog, {
-                                       closable: false,
-                                       onClose: function() {
-                                               if (ControllerCaptcha.has('commentAdd')) {
-                                                       ControllerCaptcha.delete('commentAdd');
-                                               }
-                                       },
-                                       title: Language.get('wcf.global.confirmation.title')
-                               });
-                               
-                               var dialog = UiDialog.getDialog('jsDialogGuestComment');
-                               elBySel('input[type=submit]', dialog.content).addEventListener(WCF_CLICK_EVENT, this._submitGuestDialog.bind(this));
-                               elBySel('button[data-type="cancel"]', dialog.content).addEventListener(WCF_CLICK_EVENT, this._cancelGuestDialog.bind(this));
-                               elBySel('input[type=text]', dialog.content).addEventListener('keypress', this._submitGuestDialog.bind(this));
-                       }
-                       else {
-                               var scrollTarget = this._insertMessage(data);
-                               
-                               if (!User.userId) {
-                                       UiDialog.close('jsDialogGuestComment');
-                               }
-                               
-                               this._reset();
-                               
-                               this._hideLoadingOverlay();
-                               
-                               window.setTimeout((function () {
-                                       UiScroll.element(scrollTarget);
-                               }).bind(this), 100);
-                       }
-               },
-               
-               _ajaxFailure: function(data) {
-                       this._hideLoadingOverlay();
-                       
-                       //noinspection JSUnresolvedVariable
-                       if (data === null || data.returnValues === undefined || data.returnValues.errorType === undefined) {
-                               return true;
-                       }
-                       
-                       this._handleError(data);
-                       
-                       return false;
-               },
-               
-               _ajaxSetup: function() {
-                       return {
-                               data: {
-                                       actionName: 'addComment',
-                                       className: 'wcf\\data\\comment\\CommentAction'
-                               },
-                               silent: true
-                       };
-               },
-               
-               /**
-                * Cancels the guest dialog and restores the comment editor.
-                */
-               _cancelGuestDialog: function() {
-                       UiDialog.close('jsDialogGuestComment');
-                       
-                       this._hideLoadingOverlay();
-               }
-       };
-       
-       return UiCommentAdd;
-});
-
-/**
- * Provides editing support for comments.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Comment/Edit
- */
-define(
-       'WoltLabSuite/Core/Ui/Comment/Edit',[
-               'Ajax',         'Core',            'Dictionary',          'Environment',
-               'EventHandler', 'Language',        'List',                'Dom/ChangeListener', 'Dom/Traverse',
-               'Dom/Util',     'Ui/Notification', 'Ui/ReusableDropdown', 'WoltLabSuite/Core/Ui/Scroll'
-       ],
-       function(
-               Ajax,            Core,              Dictionary,            Environment,
-               EventHandler,    Language,          List,                  DomChangeListener,    DomTraverse,
-               DomUtil,         UiNotification,    UiReusableDropdown,    UiScroll
-       )
-{
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       rebuild: function() {},
-                       _click: function() {},
-                       _prepare: function() {},
-                       _showEditor: function() {},
-                       _restoreMessage: function() {},
-                       _save: function() {},
-                       _validate: function() {},
-                       throwError: function() {},
-                       _showMessage: function() {},
-                       _hideEditor: function() {},
-                       _restoreEditor: function() {},
-                       _destroyEditor: function() {},
-                       _getEditorId: function() {},
-                       _getObjectId: function() {},
-                       _ajaxFailure: function() {},
-                       _ajaxSuccess: function() {},
-                       _ajaxSetup: function() {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * @constructor
-        */
-       function UiCommentEdit(container) { this.init(container); }
-       UiCommentEdit.prototype = {
-               /**
-                * Initializes the comment edit manager.
-                * 
-                * @param       {Element}       container       container element
-                */
-               init: function(container) {
-                       this._activeElement = null;
-                       this._callbackClick = null;
-                       this._comments = new List();
-                       this._container = container;
-                       this._editorContainer = null;
-                       
-                       this.rebuild();
-                       
-                       DomChangeListener.add('Ui/Comment/Edit_' + DomUtil.identify(this._container), this.rebuild.bind(this));
-               },
-               
-               /**
-                * Initializes each applicable message, should be called whenever new
-                * messages are being displayed.
-                */
-               rebuild: function() {
-                       elBySelAll('.comment', this._container, (function (comment) {
-                               if (this._comments.has(comment)) {
-                                       return;
-                               }
-                               
-                               if (elDataBool(comment, 'can-edit')) {
-                                       var button = elBySel('.jsCommentEditButton', comment);
-                                       if (button !== null) {
-                                               if (this._callbackClick === null) {
-                                                       this._callbackClick = this._click.bind(this);
-                                               }
-                                               
-                                               button.addEventListener(WCF_CLICK_EVENT, this._callbackClick);
-                                       }
-                               }
-                               
-                               this._comments.add(comment);
-                       }).bind(this));
-               },
-               
-               /**
-                * Handles clicks on the edit button.
-                * 
-                * @param       {?Event}        event           event object
-                * @protected
-                */
-               _click: function(event) {
-                       event.preventDefault();
-                       
-                       if (this._activeElement === null) {
-                               this._activeElement = event.currentTarget.closest('.comment');
-                               
-                               this._prepare();
-                               
-                               Ajax.api(this, {
-                                       actionName: 'beginEdit',
-                                       objectIDs: [this._getObjectId(this._activeElement)]
-                               });
-                       }
-                       else {
-                               UiNotification.show('wcf.message.error.editorAlreadyInUse', null, 'warning');
-                       }
-               },
-               
-               /**
-                * Prepares the message for editor display.
-                * 
-                * @protected
-                */
-               _prepare: function() {
-                       this._editorContainer = elCreate('div');
-                       this._editorContainer.className = 'commentEditorContainer';
-                       this._editorContainer.innerHTML = '<span class="icon icon48 fa-spinner"></span>';
-                       
-                       var content = elBySel('.commentContentContainer', this._activeElement);
-                       content.insertBefore(this._editorContainer, content.firstChild);
-               },
-               
-               /**
-                * Shows the message editor.
-                * 
-                * @param       {Object}        data            ajax response data
-                * @protected
-                */
-               _showEditor: function(data) {
-                       var id = this._getEditorId();
-                       
-                       var icon = elBySel('.icon', this._editorContainer);
-                       elRemove(icon);
-                       
-                       var editor = elCreate('div');
-                       editor.className = 'editorContainer';
-                       //noinspection JSUnresolvedVariable
-                       DomUtil.setInnerHtml(editor, data.returnValues.template);
-                       this._editorContainer.appendChild(editor);
-                       
-                       // bind buttons
-                       var formSubmit = elBySel('.formSubmit', editor);
-                       
-                       var buttonSave = elBySel('button[data-type="save"]', formSubmit);
-                       buttonSave.addEventListener(WCF_CLICK_EVENT, this._save.bind(this));
-                       
-                       var buttonCancel = elBySel('button[data-type="cancel"]', formSubmit);
-                       buttonCancel.addEventListener(WCF_CLICK_EVENT, this._restoreMessage.bind(this));
-                       
-                       EventHandler.add('com.woltlab.wcf.redactor', 'submitEditor_' + id, (function(data) {
-                               data.cancel = true;
-                               
-                               this._save();
-                       }).bind(this));
-                       
-                       var editorElement = elById(id);
-                       if (Environment.editor() === 'redactor') {
-                               window.setTimeout((function() {
-                                       UiScroll.element(this._activeElement);
-                               }).bind(this), 250);
-                       }
-                       else {
-                               editorElement.focus();
-                       }
-               },
-               
-               /**
-                * Restores the message view.
-                * 
-                * @protected
-                */
-               _restoreMessage: function() {
-                       this._destroyEditor();
-                       
-                       elRemove(this._editorContainer);
-                       
-                       this._activeElement = null;
-               },
-               
-               /**
-                * Saves the editor message.
-                * 
-                * @protected
-                */
-               _save: function() {
-                       var parameters = {
-                               data: {
-                                       message: ''
-                               }
-                       };
-                       
-                       var id = this._getEditorId();
-                       
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'getText_' + id, parameters.data);
-                       
-                       if (!this._validate(parameters)) {
-                               // validation failed
-                               return;
-                       }
-                       
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'submit_' + id, parameters);
-                       
-                       Ajax.api(this, {
-                               actionName: 'save',
-                               objectIDs: [this._getObjectId(this._activeElement)],
-                               parameters: parameters
-                       });
-                       
-                       this._hideEditor();
-               },
-               
-               /**
-                * Validates the message and invokes listeners to perform additional validation.
-                *
-                * @param       {Object}        parameters      request parameters
-                * @return      {boolean}       validation result
-                * @protected
-                */
-               _validate: function(parameters) {
-                       // remove all existing error elements
-                       elBySelAll('.innerError', this._activeElement, elRemove);
-                       
-                       // check if editor contains actual content
-                       var editorElement = elById(this._getEditorId());
-                       if (window.jQuery(editorElement).data('redactor').utils.isEmpty()) {
-                               this.throwError(editorElement, Language.get('wcf.global.form.error.empty'));
-                               return false;
-                       }
-                       
-                       var data = {
-                               api: this,
-                               parameters: parameters,
-                               valid: true
-                       };
-                       
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'validate_' + this._getEditorId(), data);
-                       
-                       return (data.valid !== false);
-               },
-               
-               /**
-                * Throws an error by adding an inline error to target element.
-                *
-                * @param       {Element}       element         erroneous element
-                * @param       {string}        message         error message
-                */
-               throwError: function(element, message) {
-                       elInnerError(element, message);
-               },
-               
-               /**
-                * Shows the update message.
-                * 
-                * @param       {Object}        data            ajax response data
-                * @protected
-                */
-               _showMessage: function(data) {
-                       // set new content
-                       //noinspection JSCheckFunctionSignatures
-                       DomUtil.setInnerHtml(elBySel('.commentContent .userMessage', this._editorContainer.parentNode), data.returnValues.message);
-                       
-                       this._restoreMessage();
-                       
-                       UiNotification.show();
-               },
-               
-               /**
-                * Hides the editor from view.
-                * 
-                * @protected
-                */
-               _hideEditor: function() {
-                       elHide(elBySel('.editorContainer', this._editorContainer));
-                       
-                       var icon = elCreate('span');
-                       icon.className = 'icon icon48 fa-spinner';
-                       this._editorContainer.appendChild(icon);
-               },
-               
-               /**
-                * Restores the previously hidden editor.
-                * 
-                * @protected
-                */
-               _restoreEditor: function() {
-                       var icon = elBySel('.fa-spinner', this._editorContainer);
-                       elRemove(icon);
-                       
-                       var editorContainer = elBySel('.editorContainer', this._editorContainer);
-                       if (editorContainer !== null) elShow(editorContainer);
-               },
-               
-               /**
-                * Destroys the editor instance.
-                * 
-                * @protected
-                */
-               _destroyEditor: function() {
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'autosaveDestroy_' + this._getEditorId());
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'destroy_' + this._getEditorId());
-               },
-               
-               /**
-                * Returns the unique editor id.
-                * 
-                * @return      {string}        editor id
-                * @protected
-                */
-               _getEditorId: function() {
-                       return 'commentEditor' + this._getObjectId(this._activeElement);
-               },
-               
-               /**
-                * Returns the element's `data-object-id` value.
-                * 
-                * @param       {Element}       element         target element
-                * @return      {int}
-                * @protected
-                */
-               _getObjectId: function(element) {
-                       return ~~elData(element, 'object-id');
-               },
-               
-               _ajaxFailure: function(data) {
-                       var editor = elBySel('.redactor-layer', this._editorContainer);
-                       
-                       // handle errors occurring on editor load
-                       if (editor === null) {
-                               this._restoreMessage();
-                               
-                               return true;
-                       }
-                       
-                       this._restoreEditor();
-                       
-                       //noinspection JSUnresolvedVariable
-                       if (!data || data.returnValues === undefined || data.returnValues.errorType === undefined) {
-                               return true;
-                       }
-                       
-                       //noinspection JSUnresolvedVariable
-                       elInnerError(editor, data.returnValues.errorType);
-                       
-                       return false;
-               },
-               
-               _ajaxSuccess: function(data) {
-                       switch (data.actionName) {
-                               case 'beginEdit':
-                                       this._showEditor(data);
-                                       break;
-                                       
-                               case 'save':
-                                       this._showMessage(data);
-                                       break;
-                       }
-               },
-               
-               _ajaxSetup: function() {
-                       var objectTypeId = ~~elData(this._container, 'object-type-id');
-                       
-                       return {
-                               data: {
-                                       className: 'wcf\\data\\comment\\CommentAction',
-                                       parameters: {
-                                               data: {
-                                                       objectTypeID: objectTypeId
-                                               }
-                                       }
-                               },
-                               silent: true
-                       };
-               }
-       };
-       
-       return UiCommentEdit;
-});
-
-/**
- * Simplified and consistent dropdown creation.
- * 
- * @author      Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module      WoltLabSuite/Core/Ui/Dropdown/Builder
- */
-define('WoltLabSuite/Core/Ui/Dropdown/Builder',['Core', 'Ui/SimpleDropdown'], function (Core, UiSimpleDropdown) {
-       "use strict";
-       
-       var _validIconSizes = [16, 24, 32, 48, 64, 96, 144];
-       
-       function _validateList(list) {
-               if (!(list instanceof HTMLUListElement)) {
-                       throw new TypeError('Expected a reference to an <ul> element.');
-               }
-               
-               if (!list.classList.contains('dropdownMenu')) {
-                       throw new Error('List does not appear to be a dropdown menu.');
-               }
-       }
-       
-       function _buildItem(data) {
-               var item = elCreate('li');
-               
-               // handle special `divider` type
-               if (data === 'divider') {
-                       item.className = 'dropdownDivider';
-                       return item;
-               }
-               
-               if (typeof data.identifier === 'string') {
-                       elData(item, 'identifier', data.identifier);
-               }
-               
-               var link = elCreate('a');
-               link.href = (typeof data.href === 'string') ? data.href : '#';
-               if (typeof data.callback === 'function') {
-                       link.addEventListener(WCF_CLICK_EVENT, function (event) {
-                               event.preventDefault();
-                               
-                               data.callback(link);
-                       });
-               }
-               else if (link.getAttribute('href') === '#') {
-                       throw new Error('Expected either a `href` value or a `callback`.');
-               }
-               
-               if (data.hasOwnProperty('attributes') && Core.isPlainObject(data.attributes)) {
-                       for (var key in data.attributes) {
-                               if (data.attributes.hasOwnProperty(key)) {
-                                       elData(link, key, data.attributes[key]);
-                               }
-                       }
-               }
-               
-               item.appendChild(link);
-               
-               if (typeof data.icon !== 'undefined' && Core.isPlainObject(data.icon)) {
-                       if (typeof data.icon.name !== 'string') {
-                               throw new TypeError('Expected a valid icon name.');
-                       }
-                       
-                       var size = 16;
-                       if (typeof data.icon.size === 'number' && _validIconSizes.indexOf(~~data.icon.size) !== -1) {
-                               size = ~~data.icon.size;
-                       }
-                       
-                       var icon = elCreate('span');
-                       icon.className = 'icon icon' + size + ' fa-' + data.icon.name;
-                       
-                       link.appendChild(icon);
-               }
-               
-               var label = (typeof data.label === 'string') ? data.label.trim() : '';
-               var labelHtml = (typeof data.labelHtml === 'string') ? data.labelHtml.trim() : '';
-               if (label === '' && labelHtml === '') {
-                       throw new TypeError('Expected either a label or a `labelHtml`.');
-               }
-               
-               var span = elCreate('span');
-               span[label ? 'textContent' : 'innerHTML'] = (label) ? label : labelHtml;
-               link.appendChild(document.createTextNode(' '));
-               link.appendChild(span);
-               
-               return item;
-       }
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Dropdown/Builder
-        */
-       return {
-               /**
-                * Creates a new dropdown menu, optionally pre-populated with the supplied list of
-                * dropdown items. The list element will be returned and must be manually injected
-                * into the DOM by the callee.
-                * 
-                * @param       {(Object|string)[]}     items
-                * @param       {string?}               identifier
-                * @return      {Element}
-                */
-               create: function (items, identifier) {
-                       var list = elCreate('ul');
-                       list.className = 'dropdownMenu';
-                       if (typeof identifier === 'string') {
-                               elData(list, 'identifier', identifier);
-                       }
-                       
-                       if (Array.isArray(items) && items.length > 0) {
-                               this.appendItems(list, items);
-                       }
-                       
-                       return list;
-               },
-               
-               /**
-                * Creates a new dropdown item that can be inserted into lists using regular DOM operations.
-                * 
-                * @param       {(Object|string)}        item
-                * @return      {Element}
-                */
-               buildItem: function (item) {
-                       return _buildItem(item);
-               },
-               
-               /**
-                * Appends a single item to the target list.
-                * 
-                * @param       {Element}               list
-                * @param       {(Object|string)}       item
-                */
-               appendItem: function (list, item) {
-                       _validateList(list);
-                       
-                       list.appendChild(_buildItem(item));
-               },
-               
-               /**
-                * Appends a list of items to the target list.
-                * 
-                * @param       {Element}               list
-                * @param       {(Object|string)[]}     items
-                */
-               appendItems: function (list, items) {
-                       _validateList(list);
-                       
-                       if (!Array.isArray(items)) {
-                               throw new TypeError('Expected an array of items.');
-                       }
-                       
-                       var length = items.length;
-                       if (length === 0) {
-                               throw new Error('Expected a non-empty list of items.');
-                       }
-                       
-                       if (length === 1) {
-                               this.appendItem(list, items[0]);
-                       }
-                       else {
-                               var fragment = document.createDocumentFragment();
-                               for (var i = 0; i < length; i++) {
-                                       fragment.appendChild(_buildItem(items[i]));
-                               }
-                               list.appendChild(fragment);
-                       }
-               },
-               
-               /**
-                * Replaces the existing list items with the provided list of new items.
-                * 
-                * @param       {Element}               list
-                * @param       {(Object|string)[]}     items
-                */
-               setItems: function (list, items) {
-                       _validateList(list);
-                       
-                       list.innerHTML = '';
-                       
-                       this.appendItems(list, items);
-               },
-               
-               /**
-                * Attaches the list to a button, visibility is from then on controlled through clicks
-                * on the provided button element. Internally calls `Ui/SimpleDropdown.initFragment()`
-                * to delegate the DOM management.
-                * 
-                * @param       {Element}               list
-                * @param       {Element}               button
-                */
-               attach: function (list, button) {
-                       _validateList(list);
-                       
-                       UiSimpleDropdown.initFragment(button, list);
-                       
-                       button.addEventListener(WCF_CLICK_EVENT, function (event) {
-                               event.preventDefault();
-                               event.stopPropagation();
-                               
-                               UiSimpleDropdown.toggleDropdown(button.id);
-                       });
-               },
-               
-               /**
-                * Helper method that returns the special string `"divider"` that causes a divider to
-                * be created.
-                * 
-                * @return      {string}
-                */
-               divider: function () {
-                       return 'divider';
-               }
-       };
-});
-
-/**
- * Delete files which are uploaded via AJAX.
- *
- * @author     Joshua Ruesweg
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/File/Delete
- * @since      5.2
- */
-define('WoltLabSuite/Core/Ui/File/Delete',['Ajax', 'Core', 'Dom/ChangeListener', 'Language', 'Dom/Util', 'Dom/Traverse', 'Dictionary'], function(Ajax, Core, DomChangeListener, Language, DomUtil, DomTraverse, Dictionary) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function Delete(buttonContainerId, targetId, isSingleImagePreview, uploadHandler) {
-               this._isSingleImagePreview = isSingleImagePreview;
-               this._uploadHandler = uploadHandler;
-               
-               this._buttonContainer = elById(buttonContainerId);
-               if (this._buttonContainer === null) {
-                       throw new Error("Element id '" + buttonContainerId + "' is unknown.");
-               }
-               
-               this._target = elById(targetId);
-               if (targetId === null) {
-                       throw new Error("Element id '" + targetId + "' is unknown.");
-               }
-               this._containers = new Dictionary();
-               
-               this._internalId = elData(this._target, 'internal-id');
-               
-               if (!this._internalId) {
-                       throw new Error("InternalId is unknown.");
-               }
-               
-               this.rebuild();
-       }
-       
-       Delete.prototype = {
-               /**
-                * Creates the upload button.
-                */
-               _createButtons: function() {
-                       var element, elements = elBySelAll('li.uploadedFile', this._target), elementData, triggerChange = false, uniqueFileId;
-                       for (var i = 0, length = elements.length; i < length; i++) {
-                               element = elements[i];
-                               uniqueFileId = elData(element, 'unique-file-id');
-                               if (this._containers.has(uniqueFileId)) {
-                                       continue;
-                               }
-                               
-                               elementData = {
-                                       uniqueFileId: uniqueFileId,
-                                       element: element
-                               };
-                               
-                               this._containers.set(uniqueFileId, elementData);
-                               this._initDeleteButton(element, elementData);
-                               
-                               triggerChange = true;
-                       }
-                       
-                       if (triggerChange) {
-                               DomChangeListener.trigger();
-                       }
-               },
-               
-               /**
-                * Init the delete button for a specific element.
-                * 
-                * @param       {HTMLElement}   element
-                * @param       {string}        elementData
-                */
-               _initDeleteButton: function(element, elementData) {
-                       var buttonGroup = elBySel('.buttonGroup', element);
-                       
-                       if (buttonGroup === null) {
-                               throw new Error("Button group in '" + targetId + "' is unknown.");
-                       }
-                       
-                       var li = elCreate('li');
-                       var span = elCreate('span');
-                       span.classList = "button jsDeleteButton small";
-                       span.textContent = Language.get('wcf.global.button.delete');
-                       li.appendChild(span);
-                       buttonGroup.appendChild(li);
-                       
-                       li.addEventListener(WCF_CLICK_EVENT, this._delete.bind(this, elementData.uniqueFileId));
-               },
-               
-               /**
-                * Delete a specific file with the given uniqueFileId.
-                * 
-                * @param       {string}        uniqueFileId
-                */
-               _delete: function(uniqueFileId) {
-                       Ajax.api(this, {
-                               uniqueFileId: uniqueFileId,
-                               internalId: this._internalId
-                       });
-               },
-               
-               /**
-                * Rebuilds the delete buttons for unknown files. 
-                */
-               rebuild: function() {
-                       if (this._isSingleImagePreview) {
-                               var img = elBySel('img', this._target);
-                               
-                               if (img !== null) {
-                                       var uniqueFileId = elData(img, 'unique-file-id');
-                                       
-                                       if (!this._containers.has(uniqueFileId)) {
-                                               var elementData = {
-                                                       uniqueFileId: uniqueFileId,
-                                                       element: img
-                                               };
-                                               
-                                               this._containers.set(uniqueFileId, elementData);
-                                               
-                                               this._deleteButton = elCreate('p');
-                                               this._deleteButton.className = 'button deleteButton';
-                                               
-                                               var span = elCreate('span');
-                                               span.textContent = Language.get('wcf.global.button.delete');
-                                               this._deleteButton.appendChild(span);
-                                               
-                                               this._buttonContainer.appendChild(this._deleteButton);
-                                               
-                                               this._deleteButton.addEventListener(WCF_CLICK_EVENT, this._delete.bind(this, elementData.uniqueFileId));
-                                       }
-                               }
-                       }
-                       else {
-                               this._createButtons();
-                       }
-               },
-               
-               _ajaxSuccess: function(data) {
-                       elRemove(this._containers.get(data.uniqueFileId).element);
-                       
-                       if (this._isSingleImagePreview) {
-                               elRemove(this._deleteButton);
-                               this._deleteButton = null;
-                       }
-                       
-                       this._uploadHandler.checkMaxFiles();
-               },
-               
-               _ajaxSetup: function () {
-                       return {
-                               url: 'index.php?ajax-file-delete/&t=' + SECURITY_TOKEN
-                       };
-               }
-       };
-       
-       return Delete;
-});
-
-/**
- * Uploads file via AJAX.
- *
- * @author     Joshua Ruesweg, Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/File/Upload
- * @since      5.2
- */
-define('WoltLabSuite/Core/Ui/File/Upload',['Core', 'Language', 'Dom/Util', 'WoltLabSuite/Core/Ui/File/Delete', 'Upload'], function(Core, Language, DomUtil, DeleteHandler, CoreUpload) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function Upload(buttonContainerId, targetId, options) {
-               options = options || {};
-               
-               if (options.internalId === undefined) {
-                       throw new Error("Missing internal id.");
-               }
-               
-               // set default options
-               this._options = Core.extend({
-                       // name if the upload field
-                       name: '__files[]',
-                       // is true if every file from a multi-file selection is uploaded in its own request
-                       singleFileRequests: false,
-                       // url for uploading file
-                       url: 'index.php?ajax-file-upload/&t=' + SECURITY_TOKEN,
-                       // image preview
-                       imagePreview: false,
-                       // max files
-                       maxFiles: null
-               }, options);
-               
-               this._options.multiple = this._options.maxFiles === null || this._options.maxFiles > 1; 
-               
-               this._options.url = this._options.url;
-               if (this._options.url.indexOf('index.php') === 0) {
-                       this._options.url = WSC_API_URL + this._options.url;
-               }
-               
-               this._buttonContainer = elById(buttonContainerId);
-               if (this._buttonContainer === null) {
-                       throw new Error("Element id '" + buttonContainerId + "' is unknown.");
-               }
-               
-               this._target = elById(targetId);
-               if (targetId === null) {
-                       throw new Error("Element id '" + targetId + "' is unknown.");
-               }
-               
-               if (options.multiple && this._target.nodeName !== 'UL' && this._target.nodeName !== 'OL') {
-                       throw new Error("Target element has to be list or table body if uploading multiple files is supported.");
-               }
-               
-               this._fileElements = [];
-               this._internalFileId = 0;
-               
-               // upload ids that belong to an upload of multiple files at once
-               this._multiFileUploadIds = [];
-               
-               this._createButton();
-               this.checkMaxFiles();
-               
-               this._deleteHandler = new DeleteHandler(buttonContainerId, targetId, this._options.imagePreview, this);
-       }
-       
-       Core.inherit(Upload, CoreUpload, {
-               _createFileElement: function(file) {
-                       var element = Upload._super.prototype._createFileElement.call(this, file);
-                       element.classList.add('box64', 'uploadedFile');
-                       
-                       var progress = elBySel('progress', element);
-                       
-                       var icon = elCreate('span');
-                       icon.classList = 'icon icon64 fa-spinner';
-                       
-                       var fileName = element.textContent;
-                       element.textContent = "";
-                       element.append(icon);
-                       
-                       var innerDiv = elCreate('div');
-                       var fileNameP = elCreate('p');
-                       fileNameP.textContent = fileName; // file.name
-                       
-                       var smallProgress = elCreate('small');
-                       smallProgress.appendChild(progress);
-                       
-                       innerDiv.appendChild(fileNameP);
-                       innerDiv.appendChild(smallProgress);
-                       
-                       var div = elCreate('div');
-                       div.appendChild(innerDiv);
-                       
-                       var ul = elCreate('ul');
-                       ul.classList = 'buttonGroup';
-                       div.appendChild(ul);
-                       
-                       // reset element textContent and replace with own element style
-                       element.append(div);
-                       
-                       return element;
-               },
-               
-               _failure: function(uploadId, data, responseText, xhr, requestOptions) {
-                       for (var i = 0, length = this._fileElements[uploadId].length; i < length; i++) {
-                               this._fileElements[uploadId][i].classList.add('uploadFailed');
-                               
-                               elBySel('small', this._fileElements[uploadId][i]).innerHTML = '';
-                               var icon = elBySel('.icon', this._fileElements[uploadId][i]);
-                               icon.classList.remove('fa-spinner');
-                               icon.classList.add('fa-ban');
-                               
-                               var innerError = elCreate('span');
-                               innerError.classList = 'innerError';
-                               innerError.textContent = Language.get('wcf.upload.error.uploadFailed');
-                               DomUtil.insertAfter(innerError, elBySel('small', this._fileElements[uploadId][i]));
-                       }
-                       
-                       throw new Error("Upload failed: " + data.message);
-               },
-               
-               _upload: function(event, file, blob) {
-                       var innerError = elBySel('small.innerError:not(.innerFileError)', this._buttonContainer.parentNode);
-                       if (innerError) elRemove(innerError);
-                       
-                       return Upload._super.prototype._upload.call(this, event, file, blob);
-               },
-               
-               _success: function(uploadId, data, responseText, xhr, requestOptions) {
-                       for (var i = 0, length = this._fileElements[uploadId].length; i < length; i++) {
-                               if (data['files'][i] !== undefined) {
-                                       if (this._options.imagePreview) {
-                                               if (data['files'][i].image === null) {
-                                                       throw new Error("Expect image for uploaded file. None given.");
-                                               }
-                                               
-                                               elRemove(this._fileElements[uploadId][i]);
-                                               
-                                               if (elBySel('img.previewImage', this._target) !== null) {
-                                                       elBySel('img.previewImage', this._target).setAttribute('src', data['files'][i].image);
-                                               }
-                                               else {
-                                                       var image = elCreate('img');
-                                                       image.classList.add('previewImage');
-                                                       image.setAttribute('src', data['files'][i].image);
-                                                       image.setAttribute('style', "max-width: 100%;");
-                                                       elData(image, 'unique-file-id', data['files'][i].uniqueFileId);
-                                                       this._target.appendChild(image);
-                                               }
-                                       }
-                                       else {
-                                               elData(this._fileElements[uploadId][i], 'unique-file-id', data['files'][i].uniqueFileId);
-                                               elBySel('small', this._fileElements[uploadId][i]).textContent = data['files'][i].filesize;
-                                               var icon = elBySel('.icon', this._fileElements[uploadId][i]);
-                                               icon.classList.remove('fa-spinner');
-                                               icon.classList.add('fa-' + data['files'][i].icon);
-                                       }
-                               }
-                               else if (data['error'][i] !== undefined) {
-                                       this._fileElements[uploadId][i].classList.add('uploadFailed');
-                                       
-                                       elBySel('small', this._fileElements[uploadId][i]).innerHTML = '';
-                                       var icon = elBySel('.icon', this._fileElements[uploadId][i]);
-                                       icon.classList.remove('fa-spinner');
-                                       icon.classList.add('fa-ban');
-                                       
-                                       if (elBySel('.innerError', this._fileElements[uploadId][i]) === null) {
-                                               var innerError = elCreate('span');
-                                               innerError.classList = 'innerError';
-                                               innerError.textContent = data['error'][i].errorMessage;
-                                               DomUtil.insertAfter(innerError, elBySel('small', this._fileElements[uploadId][i]));
-                                       }
-                                       else {
-                                               elBySel('.innerError', this._fileElements[uploadId][i]).textContent = data['error'][i].errorMessage;
-                                       }
-                               }
-                               else {
-                                       throw new Error('Unknown uploaded file for uploadId ' + uploadId + '.');
-                               }
-                       }
-                       
-                       // create delete buttons
-                       this._deleteHandler.rebuild();
-                       this.checkMaxFiles();
-               },
-               
-               _getFormData: function() {
-                       return {
-                               internalId: this._options.internalId
-                       };
-               },
-               
-               validateUpload: function(files) {
-                       if (this._options.maxFiles === null || files.length + this.countFiles() <= this._options.maxFiles) {
-                               return true;
-                       }
-                       else {
-                               var innerError = elBySel('small.innerError:not(.innerFileError)', this._buttonContainer.parentNode);
-                               
-                               if (innerError === null) {
-                                       innerError = elCreate('small');
-                                       innerError.classList = 'innerError';
-                                       DomUtil.insertAfter(innerError, this._buttonContainer);
-                               }
-                               
-                               innerError.textContent = Language.get('wcf.upload.error.reachedRemainingLimit', {
-                                       maxFiles: this._options.maxFiles - this.countFiles()
-                               });
-                               
-                               return false;
-                       }
-               },
-               
-               /**
-                * Returns the count of the uploaded images.
-                * 
-                * @return {int}
-                */
-               countFiles: function() {
-                       if (this._options.imagePreview) {
-                               return elBySel('img', this._target) !== null ? 1 : 0;
-                       }
-                       else {
-                               return this._target.childElementCount;
-                       }
-               },
-               
-               /**
-                * Checks the maximum number of files and enables or disables the upload button.
-                */
-               checkMaxFiles: function() {
-                       if (this._options.maxFiles !== null && this.countFiles() >= this._options.maxFiles) {
-                               elHide(this._button);
-                       }
-                       else {
-                               elShow(this._button);
-                       }
-               }
-       });
-       
-       return Upload;
-});
-
-/**
- * Provides a filter input for checkbox lists.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/ItemList/Filter
- */
-define('WoltLabSuite/Core/Ui/ItemList/Filter',['Core', 'EventKey', 'Language', 'List', 'StringUtil', 'Dom/Util', 'Ui/SimpleDropdown'], function (Core, EventKey, Language, List, StringUtil, DomUtil, UiSimpleDropdown) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _buildItems: function() {},
-                       _prepareItem: function() {},
-                       _keyup: function() {},
-                       _toggleVisibility: function () {},
-                       _setupVisibilityFilter: function () {},
-                       _setVisibility: function () {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * Creates a new filter input.
-        * 
-        * @param       {string}        elementId       list element id
-        * @param       {Object=}       options         options
-        * @constructor
-        */
-       function UiItemListFilter(elementId, options) { this.init(elementId, options); }
-       UiItemListFilter.prototype = {
-               /**
-                * Creates a new filter input.
-                * 
-                * @param       {string}        elementId       list element id
-                * @param       {Object=}       options         options
-                */
-               init: function(elementId, options) {
-                       this._value = '';
-                       
-                       this._options = Core.extend({
-                               callbackPrepareItem: undefined,
-                               enableVisibilityFilter: true
-                       }, options);
-                       
-                       var element = elById(elementId);
-                       if (element === null) {
-                               throw new Error("Expected a valid element id, '" + elementId + "' does not match anything.");
-                       }
-                       else if (!element.classList.contains('scrollableCheckboxList') && typeof this._options.callbackPrepareItem !== 'function') {
-                               throw new Error("Filter only works with elements with the CSS class 'scrollableCheckboxList'.");
-                       }
-                       
-                       elData(element, 'filter', 'showAll');
-                       
-                       var container = elCreate('div');
-                       container.className = 'itemListFilter';
-                       
-                       element.parentNode.insertBefore(container, element);
-                       container.appendChild(element);
-                       
-                       var inputAddon = elCreate('div');
-                       inputAddon.className = 'inputAddon';
-                       
-                       var input = elCreate('input');
-                       input.className = 'long';
-                       input.type = 'text';
-                       input.placeholder = Language.get('wcf.global.filter.placeholder');
-                       input.addEventListener('keydown', function (event) {
-                               if (EventKey.Enter(event)) {
-                                       event.preventDefault();
-                               }
-                       });
-                       input.addEventListener('keyup', this._keyup.bind(this));
-                       
-                       var clearButton = elCreate('a');
-                       clearButton.href = '#';
-                       clearButton.className = 'button inputSuffix jsTooltip';
-                       clearButton.title = Language.get('wcf.global.filter.button.clear');
-                       clearButton.innerHTML = '<span class="icon icon16 fa-times"></span>';
-                       clearButton.addEventListener('click', (function(event) {
-                               event.preventDefault();
-                               
-                               this.reset();
-                       }).bind(this));
-                       
-                       inputAddon.appendChild(input);
-                       inputAddon.appendChild(clearButton);
-                       
-                       if (this._options.enableVisibilityFilter) {
-                               var visibilityButton = elCreate('a');
-                               visibilityButton.href = '#';
-                               visibilityButton.className = 'button inputSuffix jsTooltip';
-                               visibilityButton.title = Language.get('wcf.global.filter.button.visibility');
-                               visibilityButton.innerHTML = '<span class="icon icon16 fa-eye"></span>';
-                               visibilityButton.addEventListener(WCF_CLICK_EVENT, this._toggleVisibility.bind(this));
-                               inputAddon.appendChild(visibilityButton);
-                       }
-                       
-                       container.appendChild(inputAddon);
-                       
-                       this._container = container;
-                       this._dropdown = null;
-                       this._dropdownId = '';
-                       this._element = element;
-                       this._input = input;
-                       this._items = null;
-                       this._fragment = null;
-               },
-               
-               /**
-                * Resets the filter.
-                */
-               reset: function () {
-                       this._input.value = '';
-                       this._keyup();
-               },
-               
-               /**
-                * Builds the item list and rebuilds the items' DOM for easier manipulation.
-                * 
-                * @protected
-                */
-               _buildItems: function() {
-                       this._items = new List();
-                       
-                       var callback = (typeof this._options.callbackPrepareItem === 'function') ? this._options.callbackPrepareItem : this._prepareItem.bind(this);
-                       for (var i = 0, length = this._element.childElementCount; i < length; i++) {
-                               this._items.add(callback(this._element.children[i]));
-                       }
-               },
-               
-               /**
-                * Processes an item and returns the meta data.
-                * 
-                * @param       {Element}       item    current item
-                * @return      {{item: *, span: Element, text: string}}
-                * @protected
-                */
-               _prepareItem: function(item) {
-                       var label = item.children[0];
-                       var text = label.textContent.trim();
-                       
-                       var checkbox = label.children[0];
-                       while (checkbox.nextSibling) {
-                               label.removeChild(checkbox.nextSibling);
-                       }
-                       
-                       label.appendChild(document.createTextNode(' '));
-                       
-                       var span = elCreate('span');
-                       span.textContent = text;
-                       label.appendChild(span);
-                       
-                       return {
-                               item: item,
-                               span: span,
-                               text: text
-                       };
-               },
-               
-               /**
-                * Rebuilds the list on keyup, uses case-insensitive matching.
-                * 
-                * @protected
-                */
-               _keyup: function() {
-                       var value = this._input.value.trim();
-                       if (this._value === value) {
-                               return;
-                       }
-                       
-                       if (this._fragment === null) {
-                               this._fragment = document.createDocumentFragment();
-                               
-                               // set fixed height to avoid layout jumps
-                               this._element.style.setProperty('height', this._element.offsetHeight + 'px', '');
-                       }
-                       
-                       // move list into fragment before editing items, increases performance
-                       // by avoiding the browser to perform repaint/layout over and over again
-                       this._fragment.appendChild(this._element);
-                       
-                       if (this._items === null) {
-                               this._buildItems();
-                       }
-                       
-                       var regexp = new RegExp('(' + StringUtil.escapeRegExp(value) + ')', 'i');
-                       var hasVisibleItems = (value === '');
-                       this._items.forEach(function (item) {
-                               if (value === '') {
-                                       item.span.textContent = item.text;
-                                       
-                                       elShow(item.item);
-                               }
-                               else {
-                                       if (regexp.test(item.text)) {
-                                               item.span.innerHTML = item.text.replace(regexp, '<u>$1</u>');
-                                               
-                                               elShow(item.item);
-                                               hasVisibleItems = true;
-                                       }
-                                       else {
-                                               elHide(item.item);
-                                       }
-                               }
-                       });
-                       
-                       this._container.insertBefore(this._fragment.firstChild, this._container.firstChild);
-                       this._value = value;
-                       
-                       elInnerError(this._container, (hasVisibleItems) ? false : Language.get('wcf.global.filter.error.noMatches'));
-               },
-               
-               /**
-                * Toggles the visibility mode for marked items.
-                *
-                * @param       {Event}         event
-                * @protected
-                */
-               _toggleVisibility: function (event) {
-                       event.preventDefault();
-                       event.stopPropagation();
-                       
-                       var button = event.currentTarget;
-                       if (this._dropdown === null) {
-                               var dropdown = elCreate('ul');
-                               dropdown.className = 'dropdownMenu';
-                               
-                               ['activeOnly', 'highlightActive', 'showAll'].forEach((function (type) {
-                                       var link = elCreate('a');
-                                       elData(link, 'type', type);
-                                       link.href = '#';
-                                       link.textContent = Language.get('wcf.global.filter.visibility.' + type);
-                                       link.addEventListener(WCF_CLICK_EVENT, this._setVisibility.bind(this));
-                                       
-                                       var li = elCreate('li');
-                                       li.appendChild(link);
-                                       
-                                       if (type === 'showAll') {
-                                               li.className = 'active';
-                                               
-                                               var divider = elCreate('li');
-                                               divider.className = 'dropdownDivider';
-                                               dropdown.appendChild(divider);
-                                       }
-                                       
-                                       dropdown.appendChild(li);
-                               }).bind(this));
-                               
-                               UiSimpleDropdown.initFragment(button, dropdown);
-                               
-                               // add `active` classes required for the visibility filter
-                               this._setupVisibilityFilter();
-                               
-                               this._dropdown = dropdown;
-                               this._dropdownId = button.id;
-                       }
-                       
-                       UiSimpleDropdown.toggleDropdown(button.id, button);
-               },
-               
-               /**
-                * Set-ups the visibility filter by assigning an active class to the
-                * list items that hold the checkboxes and observing the checkboxes
-                * for any changes.
-                *
-                * This process involves quite a few DOM changes and new event listeners,
-                * therefore we'll delay this until the filter has been accessed for
-                * the first time, because none of these changes matter before that.
-                *
-                * @protected
-                */
-               _setupVisibilityFilter: function () {
-                       var nextSibling = this._element.nextSibling;
-                       var parent = this._element.parentNode;
-                       var scrollTop = this._element.scrollTop;
-                       
-                       // mass-editing of DOM elements is slow while they're part of the document 
-                       var fragment = document.createDocumentFragment();
-                       fragment.appendChild(this._element);
-                       
-                       elBySelAll('li', this._element, function(li) {
-                               var checkbox = elBySel('input[type="checkbox"]', li);
-                               if (checkbox) {
-                                       if (checkbox.checked) li.classList.add('active');
-                                       
-                                       checkbox.addEventListener('change', function() {
-                                               li.classList[(checkbox.checked ? 'add' : 'remove')]('active');
-                                       });
-                               }
-                               else {
-                                       var radioButton = elBySel('input[type="radio"]', li);
-                                       if (radioButton) {
-                                               if (radioButton.checked) li.classList.add('active');
-                                               
-                                               radioButton.addEventListener('change', function() {
-                                                       elBySelAll('li', this._element, function(everyLi) {
-                                                               everyLi.classList.remove('active');
-                                                       });
-                                                       
-                                                       li.classList[(radioButton.checked ? 'add' : 'remove')]('active');
-                                               }.bind(this));
-                                       }
-                               }
-                       }.bind(this));
-                       
-                       // re-insert the modified DOM
-                       parent.insertBefore(this._element, nextSibling);
-                       this._element.scrollTop = scrollTop;
-               },
-               
-               /**
-                * Sets the visibility of marked items.
-                *
-                * @param       {Event}         event
-                * @protected
-                */
-               _setVisibility: function (event) {
-                       event.preventDefault();
-                       
-                       var link = event.currentTarget;
-                       var type = elData(link, 'type');
-                       
-                       UiSimpleDropdown.close(this._dropdownId);
-                       
-                       if (elData(this._element, 'filter') === type) {
-                               // filter did not change
-                               return;
-                       }
-                       
-                       elData(this._element, 'filter', type);
-                       
-                       elBySel('.active', this._dropdown).classList.remove('active');
-                       link.parentNode.classList.add('active');
-                       
-                       var button = elById(this._dropdownId);
-                       button.classList[(type === 'showAll' ? 'remove' : 'add')]('active');
-                       
-                       var icon = elBySel('.icon', button);
-                       icon.classList[(type === 'showAll' ? 'add' : 'remove')]('fa-eye');
-                       icon.classList[(type === 'showAll' ? 'remove' : 'add')]('fa-eye-slash');
-               }
-       };
-       
-       return UiItemListFilter;
-});
-
-/**
- * Flexible UI element featuring both a list of items and an input field.
- * 
- * @author     Alexander Ebert, Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/ItemList/Static
- */
-define('WoltLabSuite/Core/Ui/ItemList/Static',['Core', 'Dictionary', 'Language', 'Dom/Traverse', 'EventKey', 'Ui/SimpleDropdown'], function(Core, Dictionary, Language, DomTraverse, EventKey, UiSimpleDropdown) {
-       "use strict";
-       
-       var _activeId = '';
-       var _data = new Dictionary();
-       var _didInit = false;
-       
-       var _callbackKeyDown = null;
-       var _callbackKeyPress = null;
-       var _callbackKeyUp = null;
-       var _callbackPaste = null;
-       var _callbackRemoveItem = null;
-       var _callbackBlur = null;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/ItemList/Static
-        */
-       return {
-               /**
-                * Initializes an item list.
-                *
-                * The `values` argument must be empty or contain a list of strings or object, e.g.
-                * `['foo', 'bar']` or `[{ objectId: 1337, value: 'baz'}, {...}]`
-                *
-                * @param       {string}        elementId       input element id
-                * @param       {Array}         values          list of existing values
-                * @param       {Object}        options         option list
-                */
-               init: function(elementId, values, options) {
-                       var element = elById(elementId);
-                       if (element === null) {
-                               throw new Error("Expected a valid element id, '" + elementId + "' is invalid.");
-                       }
-                       
-                       // remove data from previous instance
-                       if (_data.has(elementId)) {
-                               var tmp = _data.get(elementId);
-                               
-                               for (var key in tmp) {
-                                       if (tmp.hasOwnProperty(key)) {
-                                               var el = tmp[key];
-                                               if (el instanceof Element && el.parentNode) {
-                                                       elRemove(el);
-                                               }
-                                       }
-                               }
-                               
-                               UiSimpleDropdown.destroy(elementId);
-                               _data.delete(elementId);
-                       }
-                       
-                       options = Core.extend({
-                               // maximum number of items this list may contain, `-1` for infinite
-                               maxItems: -1,
-                               // maximum length of an item value, `-1` for infinite
-                               maxLength: -1,
-                               
-                               // initial value will be interpreted as comma separated value and submitted as such
-                               isCSV: false,
-                               
-                               // will be invoked whenever the items change, receives the element id first and list of values second
-                               callbackChange: null,
-                               // callback once the form is about to be submitted
-                               callbackSubmit: null,
-                               // value may contain the placeholder `{$objectId}`
-                               submitFieldName: ''
-                       }, options);
-                       
-                       var form = DomTraverse.parentByTag(element, 'FORM');
-                       if (form !== null) {
-                               if (options.isCSV === false) {
-                                       if (!options.submitFieldName.length && typeof options.callbackSubmit !== 'function') {
-                                               throw new Error("Expected a valid function for option 'callbackSubmit', a non-empty value for option 'submitFieldName' or enabling the option 'submitFieldCSV'.");
-                                       }
-                                       
-                                       form.addEventListener('submit', (function() {
-                                               var values = this.getValues(elementId);
-                                               if (options.submitFieldName.length) {
-                                                       var input;
-                                                       for (var i = 0, length = values.length; i < length; i++) {
-                                                               input = elCreate('input');
-                                                               input.type = 'hidden';
-                                                               input.name = options.submitFieldName.replace(/{$objectId}/, values[i].objectId);
-                                                               input.value = values[i].value;
-                                                               
-                                                               form.appendChild(input);
-                                                       }
-                                               }
-                                               else {
-                                                       options.callbackSubmit(form, values);
-                                               }
-                                       }).bind(this));
-                               }
-                       }
-                       
-                       this._setup();
-                       
-                       var data = this._createUI(element, options);
-                       _data.set(elementId, {
-                               dropdownMenu: null,
-                               element: data.element,
-                               list: data.list,
-                               listItem: data.element.parentNode,
-                               options: options,
-                               shadow: data.shadow
-                       });
-                       
-                       values = (data.values.length) ? data.values : values;
-                       if (Array.isArray(values)) {
-                               var value;
-                               for (var i = 0, length = values.length; i < length; i++) {
-                                       value = values[i];
-                                       if (typeof value === 'string') {
-                                               value = { objectId: 0, value: value };
-                                       }
-                                       
-                                       this._addItem(elementId, value);
-                               }
-                       }
-               },
-               
-               /**
-                * Returns the list of current values.
-                *
-                * @param       {string}        elementId       input element id
-                * @return      {Array}         list of objects containing object id and value
-                */
-               getValues: function(elementId) {
-                       if (!_data.has(elementId)) {
-                               throw new Error("Element id '" + elementId + "' is unknown.");
-                       }
-                       
-                       var data = _data.get(elementId);
-                       var values = [];
-                       elBySelAll('.item > span', data.list, function(span) {
-                               values.push({
-                                       objectId: ~~elData(span, 'object-id'),
-                                       value: span.textContent
-                               });
-                       });
-                       
-                       return values;
-               },
-               
-               /**
-                * Sets the list of current values.
-                *
-                * @param       {string}        elementId       input element id
-                * @param       {Array}         values          list of objects containing object id and value
-                */
-               setValues: function(elementId, values) {
-                       if (!_data.has(elementId)) {
-                               throw new Error("Element id '" + elementId + "' is unknown.");
-                       }
-                       
-                       var data = _data.get(elementId);
-                       
-                       // remove all existing items first
-                       var i, length;
-                       var items = DomTraverse.childrenByClass(data.list, 'item');
-                       for (i = 0, length = items.length; i < length; i++) {
-                               this._removeItem(null, items[i], true);
-                       }
-                       
-                       // add new items
-                       for (i = 0, length = values.length; i < length; i++) {
-                               this._addItem(elementId, values[i]);
-                       }
-               },
-               
-               /**
-                * Binds static event listeners.
-                */
-               _setup: function() {
-                       if (_didInit) {
-                               return;
-                       }
-                       
-                       _didInit = true;
-                       
-                       _callbackKeyDown = this._keyDown.bind(this);
-                       _callbackKeyPress = this._keyPress.bind(this);
-                       _callbackKeyUp = this._keyUp.bind(this);
-                       _callbackPaste = this._paste.bind(this);
-                       _callbackRemoveItem = this._removeItem.bind(this);
-                       _callbackBlur = this._blur.bind(this);
-               },
-               
-               /**
-                * Creates the DOM structure for target element. If `element` is a `<textarea>`
-                * it will be automatically replaced with an `<input>` element.
-                *
-                * @param       {Element}       element         input element
-                * @param       {Object}        options         option list
-                */
-               _createUI: function(element, options) {
-                       var list = elCreate('ol');
-                       list.className = 'inputItemList' + (element.disabled ? ' disabled' : '');
-                       elData(list, 'element-id', element.id);
-                       list.addEventListener(WCF_CLICK_EVENT, function(event) {
-                               if (event.target === list) {
-                                       //noinspection JSUnresolvedFunction
-                                       element.focus();
-                               }
-                       });
-                       
-                       var listItem = elCreate('li');
-                       listItem.className = 'input';
-                       list.appendChild(listItem);
-                       
-                       element.addEventListener('keydown', _callbackKeyDown);
-                       element.addEventListener('keypress', _callbackKeyPress);
-                       element.addEventListener('keyup', _callbackKeyUp);
-                       element.addEventListener('paste', _callbackPaste);
-                       element.addEventListener('blur', _callbackBlur);
-                       
-                       element.parentNode.insertBefore(list, element);
-                       listItem.appendChild(element);
-                       
-                       if (options.maxLength !== -1) {
-                               elAttr(element, 'maxLength', options.maxLength);
-                       }
-                       
-                       var shadow = null, values = [];
-                       if (options.isCSV) {
-                               shadow = elCreate('input');
-                               shadow.className = 'itemListInputShadow';
-                               shadow.type = 'hidden';
-                               //noinspection JSUnresolvedVariable
-                               shadow.name = element.name;
-                               element.removeAttribute('name');
-                               
-                               list.parentNode.insertBefore(shadow, list);
-                               
-                               //noinspection JSUnresolvedVariable
-                               var value, tmp = element.value.split(',');
-                               for (var i = 0, length = tmp.length; i < length; i++) {
-                                       value = tmp[i].trim();
-                                       if (value.length) {
-                                               values.push(value);
-                                       }
-                               }
-                               
-                               if (element.nodeName === 'TEXTAREA') {
-                                       var inputElement = elCreate('input');
-                                       inputElement.type = 'text';
-                                       element.parentNode.insertBefore(inputElement, element);
-                                       inputElement.id = element.id;
-                                       
-                                       elRemove(element);
-                                       element = inputElement;
-                               }
-                       }
-                       
-                       return {
-                               element: element,
-                               list: list,
-                               shadow: shadow,
-                               values: values
-                       };
-               },
-               
-               /**
-                * Enforces the maximum number of items.
-                *
-                * @param       {string}        elementId       input element id
-                */
-               _handleLimit: function(elementId) {
-                       var data = _data.get(elementId);
-                       if (data.options.maxItems === -1) {
-                               return;
-                       }
-                       
-                       if (data.list.childElementCount - 1 < data.options.maxItems) {
-                               if (data.element.disabled) {
-                                       data.element.disabled = false;
-                                       data.element.removeAttribute('placeholder');
-                               }
-                       }
-                       else if (!data.element.disabled) {
-                               data.element.disabled = true;
-                               elAttr(data.element, 'placeholder', Language.get('wcf.global.form.input.maxItems'));
-                       }
-               },
-               
-               /**
-                * Sets the active item list id and handles keyboard access to remove an existing item.
-                *
-                * @param       {object}        event           event object
-                */
-               _keyDown: function(event) {
-                       var input = event.currentTarget;
-                       var lastItem = input.parentNode.previousElementSibling;
-                       
-                       _activeId = input.id;
-                       
-                       if (event.keyCode === 8) {
-                               // 8 = [BACKSPACE]
-                               if (input.value.length === 0) {
-                                       if (lastItem !== null) {
-                                               if (lastItem.classList.contains('active')) {
-                                                       this._removeItem(null, lastItem);
-                                               }
-                                               else {
-                                                       lastItem.classList.add('active');
-                                               }
-                                       }
-                               }
-                       }
-                       else if (event.keyCode === 27) {
-                               // 27 = [ESC]
-                               if (lastItem !== null && lastItem.classList.contains('active')) {
-                                       lastItem.classList.remove('active');
-                               }
-                       }
-               },
-               
-               /**
-                * Handles the `[ENTER]` and `[,]` key to add an item to the list.
-                *
-                * @param       {Event}         event           event object
-                */
-               _keyPress: function(event) {
-                       if (EventKey.Enter(event) || EventKey.Comma(event)) {
-                               event.preventDefault();
-                               
-                               var value = event.currentTarget.value.trim();
-                               if (value.length) {
-                                       this._addItem(event.currentTarget.id, { objectId: 0, value: value });
-                               }
-                       }
-               },
-               
-               /**
-                * Splits comma-separated values being pasted into the input field.
-                *
-                * @param       {Event}         event
-                * @protected
-                */
-               _paste: function (event) {
-                       var text = '';
-                       if (typeof window.clipboardData === 'object') {
-                               // IE11
-                               text = window.clipboardData.getData('Text');
-                       }
-                       else {
-                               text = event.clipboardData.getData('text/plain');
-                       }
-                       
-                       text.split(/,/).forEach((function(item) {
-                               item = item.trim();
-                               if (item.length !== 0) {
-                                       this._addItem(event.currentTarget.id, { objectId: 0, value: item });
-                               }
-                       }).bind(this));
-                       
-                       event.preventDefault();
-               },
-               
-               /**
-                * Handles the keyup event to unmark an item for deletion.
-                *
-                * @param       {object}        event           event object
-                */
-               _keyUp: function(event) {
-                       var input = event.currentTarget;
-                       
-                       if (input.value.length > 0) {
-                               var lastItem = input.parentNode.previousElementSibling;
-                               if (lastItem !== null) {
-                                       lastItem.classList.remove('active');
-                               }
-                       }
-               },
-               
-               /**
-                * Adds an item to the list.
-                *
-                * @param       {string}        elementId       input element id
-                * @param       {object}        value           item value
-                */
-               _addItem: function(elementId, value) {
-                       var data = _data.get(elementId);
-                       
-                       var listItem = elCreate('li');
-                       listItem.className = 'item';
-                       
-                       var content = elCreate('span');
-                       content.className = 'content';
-                       elData(content, 'object-id', value.objectId);
-                       content.textContent = value.value;
-                       listItem.appendChild(content);
-                       
-                       if (!data.element.disabled) {
-                               var button = elCreate('a');
-                               button.className = 'icon icon16 fa-times';
-                               button.addEventListener(WCF_CLICK_EVENT, _callbackRemoveItem);
-                               listItem.appendChild(button);
-                       }
-                       
-                       data.list.insertBefore(listItem, data.listItem);
-                       data.element.value = '';
-                       
-                       if (!data.element.disabled) {
-                               this._handleLimit(elementId);
-                       }
-                       var values = this._syncShadow(data);
-                       
-                       if (typeof data.options.callbackChange === 'function') {
-                               if (values === null) values = this.getValues(elementId);
-                               data.options.callbackChange(elementId, values);
-                       }
-               },
-               
-               /**
-                * Removes an item from the list.
-                *
-                * @param       {?object}       event           event object
-                * @param       {Element?}      item            list item
-                * @param       {boolean?}      noFocus         input element will not be focused if true
-                */
-               _removeItem: function(event, item, noFocus) {
-                       item = (event === null) ? item : event.currentTarget.parentNode;
-                       
-                       var parent = item.parentNode;
-                       //noinspection JSCheckFunctionSignatures
-                       var elementId = elData(parent, 'element-id');
-                       var data = _data.get(elementId);
-                       
-                       parent.removeChild(item);
-                       if (!noFocus) data.element.focus();
-                       
-                       this._handleLimit(elementId);
-                       var values = this._syncShadow(data);
-                       
-                       if (typeof data.options.callbackChange === 'function') {
-                               if (values === null) values = this.getValues(elementId);
-                               data.options.callbackChange(elementId, values);
-                       }
-               },
-               
-               /**
-                * Synchronizes the shadow input field with the current list item values.
-                *
-                * @param       {object}        data            element data
-                */
-               _syncShadow: function(data) {
-                       if (!data.options.isCSV) return null;
-                       
-                       var value = '', values = this.getValues(data.element.id);
-                       for (var i = 0, length = values.length; i < length; i++) {
-                               value += (value.length ? ',' : '') + values[i].value;
-                       }
-                       
-                       data.shadow.value = value;
-                       
-                       return values;
-               },
-               
-               /**
-                * Handles the blur event.
-                *
-                * @param       {object}        event           event object
-                */
-               _blur: function(event) {
-                       var data = _data.get(event.currentTarget.id);
-                       
-                       var currentTarget = event.currentTarget;
-                       window.setTimeout(function() {
-                               var value = currentTarget.value.trim();
-                               if (value.length) {
-                                       this._addItem(currentTarget.id, { objectId: 0, value: value });
-                               }
-                       }.bind(this), 100);
-               }
-       };
-});
-
-/**
- * Provides an item list for users and groups.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/ItemList/User
- */
-define('WoltLabSuite/Core/Ui/ItemList/User',['WoltLabSuite/Core/Ui/ItemList'], function(UiItemList) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       getValues: function() {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/ItemList/User
-        */
-       return {
-               _shadowGroups: null,
-               
-               /**
-                * Initializes user suggestion support for an element.
-                * 
-                * @param       {string}        elementId       input element id
-                * @param       {object}        options         option list
-                */
-               init: function(elementId, options) {
-                       this._shadowGroups = null;
-                       
-                       UiItemList.init(elementId, [], {
-                               ajax: {
-                                       className: 'wcf\\data\\user\\UserAction',
-                                       parameters: {
-                                               data: {
-                                                       includeUserGroups: ~~options.includeUserGroups,
-                                                       restrictUserGroupIDs: (Array.isArray(options.restrictUserGroupIDs) ? options.restrictUserGroupIDs : [])
-                                               }
-                                       }
-                               },
-                               callbackChange: (typeof options.callbackChange === 'function' ? options.callbackChange : null),
-                               callbackSyncShadow: options.csvPerType ? this._syncShadow.bind(this) : null,
-                               callbackSetupValues: (typeof options.callbackSetupValues === 'function' ? options.callbackSetupValues : null),
-                               excludedSearchValues: (Array.isArray(options.excludedSearchValues) ? options.excludedSearchValues : []),
-                               isCSV: true,
-                               maxItems: ~~options.maxItems || -1,
-                               restricted: true
-                       });
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Ui/ItemList::getValues()
-                */
-               getValues: function(elementId) {
-                       return UiItemList.getValues(elementId);
-               },
-               
-               _syncShadow: function(data) {
-                       var values = this.getValues(data.element.id);
-                       var users = [], groups = [];
-                       
-                       values.forEach(function(value) {
-                               if (value.type && value.type === 'group') groups.push(value.objectId);
-                               else users.push(value.value);
-                       });
-                       
-                       data.shadow.value = users.join(',');
-                       if (!this._shadowGroups) {
-                               this._shadowGroups = elCreate('input');
-                               this._shadowGroups.type = 'hidden';
-                               this._shadowGroups.name = data.shadow.name + 'GroupIDs';
-                               data.shadow.parentNode.insertBefore(this._shadowGroups, data.shadow);
-                       }
-                       this._shadowGroups.value = groups.join(',');
-                       
-                       return values;
-               }
-       };
-});
-
-/**
- * Object-based user list.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/User/List
- */
-define('WoltLabSuite/Core/Ui/User/List',['Ajax', 'Core', 'Dictionary', 'Dom/Util', 'Ui/Dialog', 'WoltLabSuite/Core/Ui/Pagination'], function(Ajax, Core, Dictionary, DomUtil, UiDialog, UiPagination) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function UiUserList(options) { this.init(options); }
-       UiUserList.prototype = {
-               /**
-                * Initializes the user list.
-                * 
-                * @param       {object}        options         list of initialization options
-                */
-               init: function(options) {
-                       this._cache = new Dictionary();
-                       this._pageCount = 0;
-                       this._pageNo = 1;
-                       
-                       this._options = Core.extend({
-                               className: '',
-                               dialogTitle: '',
-                               parameters: {}
-                       }, options);
-               },
-               
-               /**
-                * Opens the user list.
-                */
-               open: function() {
-                       this._pageNo = 1;
-                       this._showPage();
-               },
-               
-               /**
-                * Shows the current or given page.
-                * 
-                * @param       {int=}          pageNo          page number
-                */
-               _showPage: function(pageNo) {
-                       if (typeof pageNo === 'number') {
-                               this._pageNo = ~~pageNo;
-                       }
-                       
-                       if (this._pageCount !== 0 && (this._pageNo < 1 || this._pageNo > this._pageCount)) {
-                               throw new RangeError("pageNo must be between 1 and " + this._pageCount + " (" + this._pageNo + " given).");
-                       }
-                       
-                       if (this._cache.has(this._pageNo)) {
-                               var dialog = UiDialog.open(this, this._cache.get(this._pageNo));
-                               
-                               if (this._pageCount > 1) {
-                                       var element = elBySel('.jsPagination', dialog.content);
-                                       if (element !== null) {
-                                               new UiPagination(element, {
-                                                       activePage: this._pageNo,
-                                                       maxPage: this._pageCount,
-                                                       
-                                                       callbackSwitch: this._showPage.bind(this)
-                                               });
-                                       }
-                                       
-                                       // scroll to the list start
-                                       var container = dialog.content.parentNode;
-                                       if (container.scrollTop > 0) {
-                                               container.scrollTop = 0;
-                                       }
-                               }
-                       }
-                       else {
-                               this._options.parameters.pageNo = this._pageNo;
-                               
-                               Ajax.api(this, {
-                                       parameters: this._options.parameters
-                               });
-                       }
-               },
-               
-               _ajaxSuccess: function(data) {
-                       if (data.returnValues.pageCount !== undefined) {
-                               this._pageCount = ~~data.returnValues.pageCount;
-                       }
-                       
-                       this._cache.set(this._pageNo, data.returnValues.template);
-                       this._showPage();
-               },
-               
-               _ajaxSetup: function() {
-                       return {
-                               data: {
-                                       actionName: 'getGroupedUserList',
-                                       className: this._options.className,
-                                       interfaceName: 'wcf\\data\\IGroupedUserListAction'
-                               }
-                       };
-               },
-               
-               _dialogSetup: function() {
-                       return {
-                               id: DomUtil.getUniqueId(),
-                               options: {
-                                       title: this._options.dialogTitle
-                               },
-                               source: null
-                       };
-               }
-       };
-       
-       return UiUserList;
-});
-
-/**
- * Provides interface elements to use reactions.
- *
- * @author     Joshua Ruesweg
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Reaction/Handler
- * @since       5.2
- */
-define(
-       'WoltLabSuite/Core/Ui/Reaction/CountButtons',[
-               'Ajax',      'Core',          'Dictionary',         'Language',
-               'ObjectMap', 'StringUtil',    'Dom/ChangeListener', 'Dom/Util',
-               'Ui/Dialog'
-       ],
-       function(
-               Ajax,        Core,                        Dictionary,           Language,
-               ObjectMap,   StringUtil,                  DomChangeListener,    DomUtil,
-               UiDialog
-       )
-       {
-               "use strict";
-               
-               /**
-                * @constructor
-                */
-               function CountButtons(objectType, options) { this.init(objectType, options); }
-               CountButtons.prototype = {
-                       /**
-                        * Initializes the like handler.
-                        *
-                        * @param       {string}        objectType      object type
-                        * @param       {object}        options         initialization options
-                        */
-                       init: function(objectType, options) {
-                               if (options.containerSelector === '') {
-                                       throw new Error("[WoltLabSuite/Core/Ui/Reaction/CountButtons] Expected a non-empty string for option 'containerSelector'.");
-                               }
-                               
-                               this._containers = new Dictionary();
-                               this._objects = new Dictionary();
-                               this._objectType = objectType;
-                               
-                               this._options = Core.extend({
-                                       // selectors
-                                       summaryListSelector: '.reactionSummaryList',
-                                       containerSelector: '',
-                                       isSingleItem: false,
-                                       
-                                       // optional parameters
-                                       parameters: {
-                                               data: {}
-                                       }
-                               }, options);
-                               
-                               this.initContainers(options, objectType);
-                               
-                               DomChangeListener.add('WoltLabSuite/Core/Ui/Reaction/CountButtons-' + objectType, this.initContainers.bind(this));
-                       },
-                       
-                       /**
-                        * Initialises the containers. 
-                        */
-                       initContainers: function() {
-                               var element, elements = elBySelAll(this._options.containerSelector), elementData, triggerChange = false, objectId;
-                               for (var i = 0, length = elements.length; i < length; i++) {
-                                       element = elements[i];
-                                       if (this._containers.has(DomUtil.identify(element))) {
-                                               continue;
-                                       }
-                                       
-                                       elementData = {
-                                               reactButton: null,
-                                               summary: null,
-                                               
-                                               objectId: ~~elData(element, 'object-id'), 
-                                               element: element
-                                       };
-                                       
-                                       this._containers.set(DomUtil.identify(element), elementData);
-                                       this._initReactionCountButtons(element, elementData);
-                                       
-                                       if (!this._objects.has(~~elData(element, 'object-id'))) {
-                                               var objects = [];
-                                       }
-                                       else {
-                                               var objects = this._objects.get(~~elData(element, 'object-id'));
-                                       }
-                                       
-                                       objects.push(elementData);
-                                       
-                                       this._objects.set(~~elData(element, 'object-id'), objects);
-                                       
-                                       triggerChange = true;
-                               }
-                               
-                               if (triggerChange) {
-                                       DomChangeListener.trigger();
-                               }
-                       },
-                       
-                       /**
-                        * Update the count buttons with the given data. 
-                        * 
-                        * @param       {int}           objectId
-                        * @param       {object}        data
-                        */
-                       updateCountButtons: function(objectId, data) {
-                               var triggerChange = false;
-                               this._objects.get(objectId).forEach(function(elementData) {
-                                       var summaryList = elBySel(this._options.summaryListSelector, elementData.element);
-                                       
-                                       var sortedElements = {}, elements = elBySelAll('li', summaryList);
-                                       for (var i = 0, length = elements.length; i < length; i++) {
-                                               if (data[elData(elements[i], 'reaction-type-id')] !== undefined) {
-                                                       sortedElements[elData(elements[i], 'reaction-type-id')] = elements[i];
-                                               }
-                                               else {
-                                                       // reaction has no longer reactions
-                                                       elRemove(elements[i]);
-                                               }
-                                       }
-                                       
-                                       Object.keys(data).forEach(function(key) {
-                                               if (sortedElements[key] !== undefined) {
-                                                       var reactionCount = elBySel('.reactionCount', sortedElements[key]);
-                                                       reactionCount.innerHTML = StringUtil.shortUnit(data[key]);
-                                               }
-                                               else if (REACTION_TYPES[key] !== undefined) {
-                                                       // create element 
-                                                       var createdElement = elCreate('li');
-                                                       createdElement.className = 'reactCountButton';
-                                                       elData(createdElement, 'reaction-type-id', key);
-                                                       
-                                                       var countSpan = elCreate('span');
-                                                       countSpan.className = 'reactionCount';
-                                                       countSpan.innerHTML = StringUtil.shortUnit(data[key]);
-                                                       createdElement.appendChild(countSpan);
-                                                       
-                                                       createdElement.innerHTML = createdElement.innerHTML + REACTION_TYPES[key].renderedIcon;
-                                                       
-                                                       summaryList.appendChild(createdElement);
-                                                       
-                                                       this._initReactionCountButton(createdElement, objectId);
-                                                       
-                                                       triggerChange = true;
-                                               }
-                                       }, this);
-                               }.bind(this));
-                               
-                               if (triggerChange) {
-                                       DomChangeListener.trigger();
-                               }
-                       },
-                       
-                       /**
-                        * Initialized the reaction count buttons. 
-                        * 
-                        * @param       {element}        element
-                        * @param       {object}        elementData
-                        */
-                       _initReactionCountButtons: function(element, elementData) {
-                               if (this._options.isSingleItem) {
-                                       var summaryList = elBySel(this._options.summaryListSelector);
-                               }
-                               else {
-                                       var summaryList = elBySel(this._options.summaryListSelector, element);
-                               }
-                               
-                               if (summaryList !== null) {
-                                       var elements = elBySelAll('li', summaryList);
-                                       for (var i = 0, length = elements.length; i < length; i++) {
-                                               this._initReactionCountButton(elements[i], elementData.objectId);
-                                       }
-                               }
-                       },
-                       
-                       /**
-                        * Initialized a specific reaction count button for an object.
-                        *
-                        * @param       {element}        element
-                        * @param       {int}            objectId
-                        */
-                       _initReactionCountButton: function(element, objectId) {
-                               element.addEventListener(WCF_CLICK_EVENT, this._showReactionOverlay.bind(this, objectId));
-                       },
-                       
-                       /**
-                        * Shows the reaction overly for a specific object. 
-                        *
-                        * @param       {int}        objectId
-                        */
-                       _showReactionOverlay: function(objectId) {
-                               this._currentObjectId = objectId;
-                               this._showOverlay();
-                       },
-                       
-                       /**
-                        * Shows a specific page of the current opened reaction overlay. 
-                        *
-                        * @param       {int}        pageNo
-                        */
-                       _showOverlay: function() {
-                               this._options.parameters.data.containerID = this._objectType + '-' + this._currentObjectId;
-                               this._options.parameters.data.objectID = this._currentObjectId;
-                               this._options.parameters.data.objectType = this._objectType;
-                               
-                               Ajax.api(this, {
-                                       parameters: this._options.parameters
-                               });
-                       },
-                       
-                       _ajaxSuccess: function(data) {
-                               UiDialog.open(this, data.returnValues.template);
-                               UiDialog.setTitle('userReactionOverlay-' + this._objectType, data.returnValues.title);
-                       },
-                       
-                       _ajaxSetup: function() {
-                               return {
-                                       data: {
-                                               actionName: 'getReactionDetails',
-                                               className: '\\wcf\\data\\reaction\\ReactionAction'
-                                       }
-                               };
-                       },
-                       
-                       _dialogSetup: function() {
-                               return {
-                                       id: 'userReactionOverlay-' + this._objectType,
-                                       options: {
-                                               title: ""
-                                       },
-                                       source: null
-                               };
-                       }
-               };
-               
-               return CountButtons;
-       });
-
-/**
- * Provides interface elements to use reactions.
- *
- * @author     Joshua Ruesweg
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Reaction/Handler
- * @since       5.2
- */
-define(
-       'WoltLabSuite/Core/Ui/Reaction/Handler',[
-               'Ajax',      'Core',                            'Dictionary',           'Language',
-               'ObjectMap', 'StringUtil',                      'Dom/ChangeListener',   'Dom/Util',
-               'Ui/Dialog', 'WoltLabSuite/Core/Ui/User/List',  'User',                 'WoltLabSuite/Core/Ui/Reaction/CountButtons',
-               'Ui/Alignment', 'Ui/CloseOverlay',              'Ui/Screen'
-       ],
-       function(
-               Ajax,        Core,              Dictionary,             Language,
-               ObjectMap,   StringUtil,        DomChangeListener,      DomUtil,
-               UiDialog,    UiUserList,        User,                   CountButtons,
-               UiAlignment, UiCloseOverlay,    UiScreen
-       )
-       {
-               "use strict";
-               
-               /**
-                * @constructor
-                */
-               function UiReactionHandler(objectType, options) { this.init(objectType, options); }
-               UiReactionHandler.prototype = {
-                       /**
-                        * Initializes the reaction handler.
-                        * 
-                        * @param       {string}        objectType      object type
-                        * @param       {object}        options         initialization options
-                        */
-                       init: function(objectType, options) {
-                               if (options.containerSelector === '') {
-                                       throw new Error("[WoltLabSuite/Core/Ui/Reaction/Handler] Expected a non-empty string for option 'containerSelector'.");
-                               }
-                               
-                               this._containers = new Dictionary();
-                               this._details = new ObjectMap();
-                               this._objectType = objectType;
-                               this._cache = new Dictionary();
-                               this._objects = new Dictionary();
-                               
-                               this._popoverCurrentObjectId = 0;
-                               
-                               this._popover = null;
-                               
-                               this._options = Core.extend({
-                                       // selectors
-                                       buttonSelector: '.reactButton',
-                                       containerSelector: '',
-                                       isButtonGroupNavigation: false,
-                                       isSingleItem: false,
-                                       
-                                       // other stuff
-                                       parameters: {
-                                               data: {}
-                                       }
-                               }, options);
-                               
-                               this.initReactButtons(options, objectType);
-                               
-                               this.countButtons = new CountButtons(this._objectType, this._options);
-                               
-                               DomChangeListener.add('WoltLabSuite/Core/Ui/Reaction/Handler-' + objectType, this.initReactButtons.bind(this));
-                               UiCloseOverlay.add('WoltLabSuite/Core/Ui/Reaction/Handler', this._closePopover.bind(this));
-                       },
-                       
-                       /**
-                        * Initializes all applicable react buttons with the given selector.
-                        */
-                       initReactButtons: function() {
-                               var element, elements = elBySelAll(this._options.containerSelector), elementData, triggerChange = false, objectId;
-                               for (var i = 0, length = elements.length; i < length; i++) {
-                                       element = elements[i];
-                                       if (this._containers.has(DomUtil.identify(element))) {
-                                               continue;
-                                       }
-                                       
-                                       elementData = {
-                                               reactButton: null,
-                                               objectId: ~~elData(element, 'object-id'),
-                                               element: element
-                                       };
-                                       
-                                       this._containers.set(DomUtil.identify(element), elementData);
-                                       this._initReactButton(element, elementData);
-                                       
-                                       if (!this._objects.has(~~elData(element, 'object-id'))) {
-                                               var objects = [];
-                                       }
-                                       else {
-                                               var objects = this._objects.get(~~elData(element, 'object-id'));
-                                       }
-                                       
-                                       objects.push(elementData);
-                                       
-                                       this._objects.set(~~elData(element, 'object-id'), objects);
-                                       
-                                       triggerChange = true;
-                               }
-                               
-                               if (triggerChange) {
-                                       DomChangeListener.trigger();
-                               }
-                       },
-                       
-                       
-                       /**
-                        * Initializes a specific react button.
-                        */
-                       _initReactButton: function(element, elementData) {
-                               if (this._options.isSingleItem) {
-                                       elementData.reactButton = elBySel(this._options.buttonSelector);
-                               }
-                               else {
-                                       elementData.reactButton = elBySel(this._options.buttonSelector, element);
-                               }
-                               
-                               if (elementData.reactButton === null || elementData.reactButton.length === 0) {
-                                       // the element may have no react button 
-                                       return;
-                               }
-                               
-                               if (Object.keys(REACTION_TYPES).length === 1) {
-                                       var reaction = REACTION_TYPES[Object.keys(REACTION_TYPES)[0]];
-                                       elementData.reactButton.title = reaction.title;
-                                       var textSpan = elBySel('.invisible', elementData.reactButton);
-                                       textSpan.innerText = reaction.title;
-                               }
-                               
-                               if (elementData.reactButton.closest('.messageFooterGroup > .jsMobileNavigation')) {
-                                       UiScreen.on('screen-sm-down', {
-                                               match: this._enableMobileView.bind(this, elementData.reactButton, elementData.objectId),
-                                               unmatch: this._disableMobileView.bind(this, elementData.reactButton, elementData.objectId),
-                                               setup: this._setupMobileView.bind(this, elementData.reactButton, elementData.objectId)
-                                       });
-                               }
-                               
-                               elementData.reactButton.addEventListener(WCF_CLICK_EVENT, this._toggleReactPopover.bind(this, elementData.objectId, elementData.reactButton));
-                       },
-                       
-                       /**
-                        * Enables the mobile view for the reaction button.
-                        * 
-                        * @param       {Element}       element
-                        */
-                       _enableMobileView: function(element) {
-                               var messageFooterGroup = element.closest('.messageFooterGroup');
-                               
-                               elShow(elBySel('.mobileReactButton', messageFooterGroup));
-                       },
-                       
-                       /**
-                        * Disables the mobile view for the reaction button.
-                        * 
-                        * @param       {Element}       element
-                        */
-                       _disableMobileView: function(element) {
-                               var messageFooterGroup = element.closest('.messageFooterGroup');
-                               
-                               elHide(elBySel('.mobileReactButton', messageFooterGroup));
-                       },
-                       
-                       /**
-                        * Setup the mobile view for the reaction button.
-                        * 
-                        * @param       {Element}       element
-                        * @param       {int}           objectID
-                        */
-                       _setupMobileView: function(element, objectID) {
-                               var messageFooterGroup = element.closest('.messageFooterGroup');
-                               
-                               var button = elCreate('button');
-                               button.classList = 'mobileReactButton';
-                               button.innerHTML = element.innerHTML;
-                               
-                               button.addEventListener(WCF_CLICK_EVENT, this._toggleReactPopover.bind(this, objectID, button));
-                               
-                               messageFooterGroup.appendChild(button);
-                       },
-                       
-                       _updateReactButton: function(objectID, reactionTypeID) {
-                               this._objects.get(objectID).forEach(function (elementData) {
-                                       if (reactionTypeID) {
-                                               elementData.reactButton.classList.add('active');
-                                               elData(elementData.reactButton, 'reaction-type-id', reactionTypeID);
-                                       }
-                                       else {
-                                               elData(elementData.reactButton, 'reaction-type-id', 0);
-                                               elementData.reactButton.classList.remove('active');
-                                       }
-                               });
-                       },
-                       
-                       _markReactionAsActive: function() {
-                               var reactionTypeID = elData(this._objects.get(this._popoverCurrentObjectId)[0].reactButton, 'reaction-type-id');
-                               
-                               //  clear old active state
-                               var elements = elBySelAll('.reactionTypeButton.active', this._getPopover());
-                               for (var i = 0, length = elements.length; i < length; i++) {
-                                       elements[i].classList.remove('active');
-                               }
-                               
-                               if (reactionTypeID != 0) {
-                                       elBySel('.reactionTypeButton[data-reaction-type-id="'+reactionTypeID+'"]', this._getPopover()).classList.add('active');
-                               }
-                       },
-                       
-                       /**
-                        * Toggle the visibility of the react popover.
-                        * 
-                        * @param       {int}           objectId
-                        * @param       {Element}       element
-                        */
-                       _toggleReactPopover: function(objectId, element, event) {
-                               if (event !== null) {
-                                       event.preventDefault();
-                                       event.stopPropagation();
-                               }
-                               
-                               if (Object.keys(REACTION_TYPES).length === 1) {
-                                       var reaction = REACTION_TYPES[Object.keys(REACTION_TYPES)[0]];
-                                       this._popoverCurrentObjectId = objectId;
-                                       
-                                       this._react(reaction.reactionTypeID);
-                               }
-                               else {
-                                       if (this._popoverCurrentObjectId === 0 || this._popoverCurrentObjectId !== objectId) {
-                                               this._openReactPopover(objectId, element);
-                                       }
-                                       else {
-                                               this._closePopover(objectId, element);
-                                       }
-                               }
-                       },
-                       
-                       /**
-                        * Opens the react popover for a specific react button.
-                        * 
-                        * @param       {int}           objectId                objectId of the element
-                        * @param       {Element}       element                 container element
-                        */
-                       _openReactPopover: function(objectId, element) {
-                               // first close old popover, if exists 
-                               if (this._popoverCurrentObjectId !== 0) {
-                                       this._closePopover();
-                               }
-                               
-                               this._popoverCurrentObjectId = objectId;
-                               this._markReactionAsActive();
-                               
-                               UiAlignment.set(this._getPopover(), element, {
-                                       pointer: true,
-                                       horizontal: (this._options.isButtonGroupNavigation) ? 'left' :'center',
-                                       vertical: 'top'
-                               });
-                               
-                               if (this._options.isButtonGroupNavigation) {
-                                       // find nav element
-                                       var nav = element.closest('nav');
-                                       nav.style.opacity = "1";
-                               }
-                               
-                               this._getPopover().classList.remove('forceHide');
-                               this._getPopover().classList.add('active');
-                       },
-                       
-                       /**
-                        * Returns the react popover element.
-                        * 
-                        * @returns {Element}
-                        */
-                       _getPopover: function() {
-                               if (this._popover == null) {
-                                       this._popover = elCreate('div');
-                                       this._popover.className = 'reactionPopover forceHide';
-                                       
-                                       var _popoverContent = elCreate('div');
-                                       _popoverContent.className = 'reactionPopoverContent';
-                                       
-                                       var popoverContentHTML = elCreate('ul');
-                                       
-                                       var sortedReactionTypes = this._getSortedReactionTypes();
-                                       
-                                       for (var key in sortedReactionTypes) {
-                                               if (!sortedReactionTypes.hasOwnProperty(key)) continue;
-                                               
-                                               var reactionType = sortedReactionTypes[key];
-                                               
-                                               var reactionTypeItem = elCreate('li');
-                                               reactionTypeItem.className = 'reactionTypeButton jsTooltip';
-                                               elData(reactionTypeItem, 'reaction-type-id', reactionType.reactionTypeID);
-                                               elData(reactionTypeItem, 'title', reactionType.title);
-                                               reactionTypeItem.title = reactionType.title;
-                                               
-                                               var reactionTypeItemSpan = elCreate('span');
-                                               reactionTypeItemSpan.classList = 'reactionTypeButtonTitle';
-                                               reactionTypeItemSpan.innerHTML = reactionType.title;
-                                               
-                                               reactionTypeItem.innerHTML = reactionType.renderedIcon;
-                                               
-                                               reactionTypeItem.appendChild(reactionTypeItemSpan);
-                                               
-                                               reactionTypeItem.addEventListener(WCF_CLICK_EVENT, this._react.bind(this, reactionType.reactionTypeID));
-                                               
-                                               popoverContentHTML.appendChild(reactionTypeItem);
-                                       }
-                                       
-                                       _popoverContent.appendChild(popoverContentHTML);
-                                       this._popover.appendChild(_popoverContent);
-                                       
-                                       var pointer = elCreate('span');
-                                       pointer.className = 'elementPointer';
-                                       pointer.appendChild(elCreate('span'));
-                                       this._popover.appendChild(pointer);
-                                       
-                                       document.body.appendChild(this._popover);
-                                       
-                                       DomChangeListener.trigger();
-                               }
-                               
-                               return this._popover;
-                       },
-                       
-                       /**
-                        * Sort the reaction types by the showOrder field.
-                        * 
-                        * @returns     {Array}         the reaction types sorted by showOrder
-                        */
-                       _getSortedReactionTypes: function() {
-                               var sortedReactionTypes = [];
-                               
-                               // convert our reaction type object to an array
-                               for (var key in REACTION_TYPES) {
-                                       if (!REACTION_TYPES.hasOwnProperty(key)) continue;
-                                       sortedReactionTypes.push(REACTION_TYPES[key]);
-                               }
-                               
-                               // sort the array
-                               sortedReactionTypes.sort(function (a, b) {
-                                       return a.showOrder - b.showOrder;
-                               });
-                               
-                               return sortedReactionTypes;
-                       },
-                       
-                       /**
-                        * Closes the react popover.
-                        */
-                       _closePopover: function() {
-                               if (this._popoverCurrentObjectId !== 0) {
-                                       this._getPopover().classList.remove('active');
-                                       
-                                       if (this._options.isButtonGroupNavigation) {
-                                               this._objects.get(this._popoverCurrentObjectId).forEach(function (elementData) {
-                                                       elementData.reactButton.closest('nav').style.cssText = "";
-                                               });
-                                       }
-                                       
-                                       this._popoverCurrentObjectId = 0;
-                               }
-                       },
-                       
-                       /**
-                        * React with the given reactionTypeId on an object.
-                        * 
-                        * @param       {init}          reactionTypeId
-                        */
-                       _react: function(reactionTypeId) {
-                               this._options.parameters.reactionTypeID = reactionTypeId;
-                               this._options.parameters.data.containerID = this._currentReactionTypeId;
-                               this._options.parameters.data.objectID = this._popoverCurrentObjectId;
-                               this._options.parameters.data.objectType = this._objectType;
-                               
-                               Ajax.api(this, {
-                                       parameters: this._options.parameters
-                               });
-                               
-                               this._closePopover();
-                       },
-                       
-                       _ajaxSuccess: function(data) {
-                               this.countButtons.updateCountButtons(data.returnValues.objectID, data.returnValues.reactions);
-                               
-                               // update react button status
-                               this._updateReactButton(data.returnValues.objectID, data.returnValues.reactionTypeID);
-                       },
-                       
-                       _ajaxSetup: function() {
-                               return {
-                                       data: {
-                                               actionName: 'react',
-                                               className: '\\wcf\\data\\reaction\\ReactionAction'
-                                       }
-                               };
-                       }
-               };
-               
-               return UiReactionHandler;
-       });
-
-/**
- * Provides interface elements to display and review likes.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Like/Handler
- * @deprecated  5.2 use ReactionHandler instead 
- */
-define(
-       'WoltLabSuite/Core/Ui/Like/Handler',[
-               'Ajax',      'Core',                     'Dictionary',         'Language',
-               'ObjectMap', 'StringUtil',               'Dom/ChangeListener', 'Dom/Util',
-               'Ui/Dialog', 'WoltLabSuite/Core/Ui/User/List', 'User',         'WoltLabSuite/Core/Ui/Reaction/Handler'
-       ],
-       function(
-               Ajax,        Core,                        Dictionary,           Language,
-               ObjectMap,   StringUtil,                  DomChangeListener,    DomUtil,
-               UiDialog,    UiUserList,                  User,                 UiReactionHandler
-       )
-{
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function UiLikeHandler(objectType, options) { this.init(objectType, options); }
-       UiLikeHandler.prototype = {
-               /**
-                * Initializes the like handler.
-                * 
-                * @param       {string}        objectType      object type
-                * @param       {object}        options         initialization options
-                */
-               init: function(objectType, options) {
-                       if (options.containerSelector === '') {
-                               throw new Error("[WoltLabSuite/Core/Ui/Like/Handler] Expected a non-empty string for option 'containerSelector'.");
-                       }
-                       
-                       this._containers = new ObjectMap();
-                       this._details = new ObjectMap();
-                       this._objectType = objectType;
-                       this._options = Core.extend({
-                               // settings
-                               badgeClassNames: '',
-                               isSingleItem: false,
-                               markListItemAsActive: false,
-                               renderAsButton: true,
-                               summaryPrepend: true,
-                               summaryUseIcon: true,
-                               
-                               // permissions
-                               canDislike: false,
-                               canLike: false,
-                               canLikeOwnContent: false,
-                               canViewSummary: false,
-                               
-                               // selectors
-                               badgeContainerSelector: '.messageHeader .messageStatus',
-                               buttonAppendToSelector: '.messageFooter .messageFooterButtons',
-                               buttonBeforeSelector: '',
-                               containerSelector: '',
-                               summarySelector: '.messageFooterGroup'
-                       }, options);
-                       
-                       this.initContainers(options, objectType);
-                       
-                       DomChangeListener.add('WoltLabSuite/Core/Ui/Like/Handler-' + objectType, this.initContainers.bind(this));
-                       
-                       new UiReactionHandler(this._objectType, {
-                               containerSelector: this._options.containerSelector,
-                               summaryListSelector: '.reactionSummaryList'
-                       });
-               },
-               
-               /**
-                * Initializes all applicable containers.
-                */
-               initContainers: function() {
-                       var element, elements = elBySelAll(this._options.containerSelector), elementData, triggerChange = false;
-                       for (var i = 0, length = elements.length; i < length; i++) {
-                               element = elements[i];
-                               if (this._containers.has(element)) {
-                                       continue;
-                               }
-                               
-                               elementData = {
-                                       badge: null,
-                                       dislikeButton: null,
-                                       likeButton: null,
-                                       summary: null,
-                                       
-                                       dislikes: ~~elData(element, 'like-dislikes'),
-                                       liked: ~~elData(element, 'like-liked'),
-                                       likes: ~~elData(element, 'like-likes'),
-                                       objectId: ~~elData(element, 'object-id'),
-                                       users: JSON.parse(elData(element, 'like-users'))
-                               };
-                               
-                               this._containers.set(element, elementData);
-                               this._buildWidget(element, elementData);
-                               
-                               triggerChange = true;
-                       }
-                       
-                       if (triggerChange) {
-                               DomChangeListener.trigger();
-                       }
-               },
-               
-               /**
-                * Creates the interface elements.
-                * 
-                * @param       {Element}       element         container element
-                * @param       {object}        elementData     like data
-                */
-               _buildWidget: function(element, elementData) {
-                       // build reaction summary list
-                       var summaryList, listItem, badgeContainer, isSummaryPosition = true;
-                       badgeContainer = (this._options.isSingleItem) ? elBySel(this._options.summarySelector) : elBySel(this._options.summarySelector, element);
-                       if (badgeContainer === null) {
-                               badgeContainer = (this._options.isSingleItem) ? elBySel(this._options.badgeContainerSelector) : elBySel(this._options.badgeContainerSelector, element);
-                               isSummaryPosition = false;
-                       }
-                       
-                       if (badgeContainer !== null) {
-                               summaryList = elCreate('ul');
-                               summaryList.classList.add('reactionSummaryList');
-                               if (isSummaryPosition) {
-                                       summaryList.classList.add('likesSummary');
-                               }
-                               else {
-                                       summaryList.classList.add('reactionSummaryListTiny');
-                               }
-                               
-                               for (var key in elementData.users) {
-                                       if (key === "reactionTypeID") continue;
-                                       if (!REACTION_TYPES.hasOwnProperty(key)) continue;
-                                       
-                                       // create element 
-                                       var createdElement = elCreate('li');
-                                       createdElement.className = 'reactCountButton';
-                                       elData(createdElement, 'reaction-type-id', key);
-                                       
-                                       var countSpan = elCreate('span');
-                                       countSpan.className = 'reactionCount';
-                                       countSpan.innerHTML = StringUtil.shortUnit(elementData.users[key]);
-                                       createdElement.appendChild(countSpan);
-                                       
-                                       createdElement.innerHTML = createdElement.innerHTML + REACTION_TYPES[key].renderedIcon;
-                                       
-                                       summaryList.appendChild(createdElement);
-                                       
-                               }
-                               
-                               if (isSummaryPosition) {
-                                       if (this._options.summaryPrepend) {
-                                               DomUtil.prepend(summaryList, badgeContainer);
-                                       }
-                                       else {
-                                               badgeContainer.appendChild(summaryList);
-                                       }
-                               }
-                               else {
-                                       if (badgeContainer.nodeName === 'OL' || badgeContainer.nodeName === 'UL') {
-                                               listItem = elCreate('li');
-                                               listItem.appendChild(summaryList);
-                                               badgeContainer.appendChild(listItem);
-                                       }
-                                       else {
-                                               badgeContainer.appendChild(summaryList);
-                                       }
-                               }
-                               
-                               elementData.badge = summaryList;
-                       }
-                       
-                       // build reaction button
-                       if (this._options.canLike && (User.userId != elData(element, 'user-id') || this._options.canLikeOwnContent)) {
-                               var appendTo = (this._options.buttonAppendToSelector) ? ((this._options.isSingleItem) ? elBySel(this._options.buttonAppendToSelector) : elBySel(this._options.buttonAppendToSelector, element)) : null;
-                               var insertPosition = (this._options.buttonBeforeSelector) ? ((this._options.isSingleItem) ? elBySel(this._options.buttonBeforeSelector) : elBySel(this._options.buttonBeforeSelector, element)) : null;
-                               if (insertPosition === null && appendTo === null) {
-                                       throw new Error("Unable to find insert location for like/dislike buttons.");
-                               }
-                               else {
-                                       elementData.likeButton = this._createButton(element, elementData.users.reactionTypeID, insertPosition, appendTo);
-                               }
-                       }
-               },
-               
-               /**
-                * Creates a reaction button.
-                * 
-                * @param       {Element}       element                 container element
-                * @param       {int}           reactionTypeID          the reactionTypeID of the current state
-                * @param       {Element?}      insertBefore            insert button before given element
-                * @param       {Element?}      appendTo                append button to given element
-                * @return      {Element}       button element 
-                */
-               _createButton: function(element, reactionTypeID, insertBefore, appendTo) {
-                       var title = Language.get('wcf.reactions.react');
-                       
-                       var listItem = elCreate('li');
-                       listItem.className = 'wcfReactButton';
-                       
-                       if (insertBefore) {
-                               var jsMobileNavigation = insertBefore.parentElement.contains('jsMobileNavigation');
-                       }
-                       else {
-                               var jsMobileNavigation = appendTo.classList.contains('jsMobileNavigation');
-                       }
-                       
-                       var button = elCreate('a');
-                       button.className = 'jsTooltip reactButton';
-                       if (this._options.renderAsButton) {
-                               button.classList.add('button');
-                               
-                               if (jsMobileNavigation) {
-                                       button.classList.add('ignoreMobileNavigation');
-                               }
-                       }
-                       
-                       button.href = '#';
-                       button.title = title;
-                       
-                       var icon = elCreate('span');
-                       icon.className = 'icon icon16 fa-smile-o';
-                       
-                       if (reactionTypeID === undefined || reactionTypeID == 0) {
-                               elData(icon, 'reaction-type-id', 0);
-                       }
-                       else {
-                               elData(button, 'reaction-type-id', reactionTypeID);
-                               button.classList.add("active");
-                       }
-                       
-                       button.appendChild(icon);
-                       
-                       var invisibleText = elCreate("span");
-                       invisibleText.className = "invisible";
-                       invisibleText.innerHTML = title;
-                       
-                       button.appendChild(document.createTextNode(" "));
-                       button.appendChild(invisibleText);
-                       
-                       listItem.appendChild(button);
-                       
-                       if (insertBefore) {
-                               insertBefore.parentNode.insertBefore(listItem, insertBefore);
-                       }
-                       else {
-                               appendTo.appendChild(listItem);
-                       }
-                       
-                       return button;
-               }
-       };
-       
-       return UiLikeHandler;
-});
-
-/**
- * Flexible message inline editor.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Message/InlineEditor
- */
-define(
-       'WoltLabSuite/Core/Ui/Message/InlineEditor',[
-               'Ajax',         'Core',            'Dictionary',          'Environment',
-               'EventHandler', 'Language',        'ObjectMap',           'Dom/ChangeListener', 'Dom/Traverse',
-               'Dom/Util',     'Ui/Notification', 'Ui/ReusableDropdown', 'WoltLabSuite/Core/Ui/Scroll'
-       ],
-       function(
-               Ajax,            Core,              Dictionary,            Environment,
-               EventHandler,    Language,          ObjectMap,             DomChangeListener,    DomTraverse,
-               DomUtil,         UiNotification,    UiReusableDropdown,    UiScroll
-       )
-{
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       rebuild: function() {},
-                       _click: function() {},
-                       _clickDropdown: function() {},
-                       _dropdownBuild: function() {},
-                       _dropdownToggle: function() {},
-                       _dropdownGetItems: function() {},
-                       _dropdownOpen: function() {},
-                       _dropdownSelect: function() {},
-                       _clickDropdownItem: function() {},
-                       _prepare: function() {},
-                       _showEditor: function() {},
-                       _restoreMessage: function() {},
-                       _save: function() {},
-                       _validate: function() {},
-                       throwError: function() {},
-                       _showMessage: function() {},
-                       _hideEditor: function() {},
-                       _restoreEditor: function() {},
-                       _destroyEditor: function() {},
-                       _getHash: function() {},
-                       _updateHistory: function() {},
-                       _getEditorId: function() {},
-                       _getObjectId: function() {},
-                       _ajaxFailure: function() {},
-                       _ajaxSuccess: function() {},
-                       _ajaxSetup: function() {},
-                       legacyEdit: function() {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * @constructor
-        */
-       function UiMessageInlineEditor(options) { this.init(options); }
-       UiMessageInlineEditor.prototype = {
-               /**
-                * Initializes the message inline editor.
-                * 
-                * @param       {Object}        options         list of configuration options
-                */
-               init: function(options) {
-                       this._activeDropdownElement = null;
-                       this._activeElement = null;
-                       this._dropdownMenu = null;
-                       this._elements = new ObjectMap();
-                       this._options = Core.extend({
-                               canEditInline: false,
-                               
-                               className: '',
-                               containerId: 0,
-                               dropdownIdentifier: '',
-                               editorPrefix: 'messageEditor',
-                               
-                               messageSelector: '.jsMessage',
-                               
-                               quoteManager: null
-                       }, options);
-                       
-                       this.rebuild();
-                       
-                       DomChangeListener.add('Ui/Message/InlineEdit_' + this._options.className, this.rebuild.bind(this));
-               },
-               
-               /**
-                * Initializes each applicable message, should be called whenever new
-                * messages are being displayed.
-                */
-               rebuild: function() {
-                       var button, canEdit, element, elements = elBySelAll(this._options.messageSelector);
-                       
-                       for (var i = 0, length = elements.length; i < length; i++) {
-                               element = elements[i];
-                               if (this._elements.has(element)) {
-                                       continue;
-                               }
-                               
-                               button = elBySel('.jsMessageEditButton', element);
-                               if (button !== null) {
-                                       canEdit = elDataBool(element, 'can-edit');
-                                       
-                                       if (this._options.canEditInline || elDataBool(element, 'can-edit-inline')) {
-                                               button.addEventListener(WCF_CLICK_EVENT, this._clickDropdown.bind(this, element));
-                                               button.classList.add('jsDropdownEnabled');
-                                               
-                                               if (canEdit) {
-                                                       button.addEventListener('dblclick', this._click.bind(this, element));
-                                               }
-                                       }
-                                       else if (canEdit) {
-                                               button.addEventListener(WCF_CLICK_EVENT, this._click.bind(this, element));
-                                       }
-                               }
-                               
-                               var messageBody = elBySel('.messageBody', element);
-                               var messageFooter = elBySel('.messageFooter', element);
-                               var messageHeader = elBySel('.messageHeader', element);
-                               
-                               this._elements.set(element, {
-                                       button: button,
-                                       messageBody: messageBody,
-                                       messageBodyEditor: null,
-                                       messageFooter: messageFooter,
-                                       messageFooterButtons: elBySel('.messageFooterButtons', messageFooter),
-                                       messageHeader: messageHeader,
-                                       messageText: elBySel('.messageText', messageBody)
-                               });
-                       }
-               },
-               
-               /**
-                * Handles clicks on the edit button or the edit dropdown item.
-                * 
-                * @param       {Element}       element         message element
-                * @param       {?Event}        event           event object
-                * @protected
-                */
-               _click: function(element, event) {
-                       if (element === null) element = this._activeDropdownElement;
-                       if (event) event.preventDefault();
-                       
-                       if (this._activeElement === null) {
-                               this._activeElement = element;
-                               
-                               this._prepare();
-                               
-                               Ajax.api(this, {
-                                       actionName: 'beginEdit',
-                                       parameters: {
-                                               containerID: this._options.containerId,
-                                               objectID: this._getObjectId(element)
-                                       }
-                               });
-                       }
-                       else {
-                               UiNotification.show('wcf.message.error.editorAlreadyInUse', null, 'warning');
-                       }
-               },
-               
-               /**
-                * Creates and opens the dropdown on first usage.
-                * 
-                * @param       {Element}       element         message element
-                * @param       {Object}        event           event object
-                * @protected
-                */
-               _clickDropdown: function(element, event) {
-                       event.preventDefault();
-                       
-                       var button = event.currentTarget;
-                       if (button.classList.contains('dropdownToggle')) {
-                               return;
-                       }
-                       
-                       button.classList.add('dropdownToggle');
-                       button.parentNode.classList.add('dropdown');
-                       (function(button, element) {
-                               button.addEventListener(WCF_CLICK_EVENT, (function(event) {
-                                       event.preventDefault();
-                                       event.stopPropagation();
-                                       
-                                       this._activeDropdownElement = element;
-                                       UiReusableDropdown.toggleDropdown(this._options.dropdownIdentifier, button);
-                               }).bind(this));
-                       }).bind(this)(button, element);
-                       
-                       // build dropdown
-                       if (this._dropdownMenu === null) {
-                               this._dropdownMenu = elCreate('ul');
-                               this._dropdownMenu.className = 'dropdownMenu';
-                               
-                               var items = this._dropdownGetItems();
-                               
-                               EventHandler.fire('com.woltlab.wcf.inlineEditor', 'dropdownInit_' + this._options.dropdownIdentifier, {
-                                       items: items
-                               });
-                               
-                               this._dropdownBuild(items);
-                               
-                               UiReusableDropdown.init(this._options.dropdownIdentifier, this._dropdownMenu);
-                               UiReusableDropdown.registerCallback(this._options.dropdownIdentifier, this._dropdownToggle.bind(this));
-                       }
-                       
-                       setTimeout(function() {
-                               Core.triggerEvent(button, WCF_CLICK_EVENT);
-                       }, 10);
-               },
-               
-               /**
-                * Creates the dropdown menu on first usage.
-                * 
-                * @param       {Object}        items   list of dropdown items
-                * @protected
-                */
-               _dropdownBuild: function(items) {
-                       var item, label, listItem;
-                       var callbackClick = this._clickDropdownItem.bind(this);
-                       
-                       for (var i = 0, length = items.length; i < length; i++) {
-                               item = items[i];
-                               listItem = elCreate('li');
-                               elData(listItem, 'item', item.item);
-                               
-                               if (item.item === 'divider') {
-                                       listItem.className = 'dropdownDivider';
-                               }
-                               else {
-                                       label = elCreate('span');
-                                       label.textContent = Language.get(item.label);
-                                       listItem.appendChild(label);
-                                       
-                                       if (item.item === 'editItem') {
-                                               listItem.addEventListener(WCF_CLICK_EVENT, this._click.bind(this, null));
-                                       }
-                                       else {
-                                               listItem.addEventListener(WCF_CLICK_EVENT, callbackClick);
-                                       }
-                               }
-                               
-                               this._dropdownMenu.appendChild(listItem);
-                       }
-               },
-               
-               /**
-                * Callback for dropdown toggle.
-                * 
-                * @param       {int}           containerId     container id
-                * @param       {string}        action          toggle action, either 'open' or 'close'
-                * @protected
-                */
-               _dropdownToggle: function(containerId, action) {
-                       var elementData = this._elements.get(this._activeDropdownElement);
-                       elementData.button.parentNode.classList[(action === 'open' ? 'add' : 'remove')]('dropdownOpen');
-                       elementData.messageFooterButtons.classList[(action === 'open' ? 'add' : 'remove')]('forceVisible');
-                       
-                       if (action === 'open') {
-                               var visibility = this._dropdownOpen();
-                               
-                               EventHandler.fire('com.woltlab.wcf.inlineEditor', 'dropdownOpen_' + this._options.dropdownIdentifier, {
-                                       element: this._activeDropdownElement,
-                                       visibility: visibility
-                               });
-                               
-                               var item, listItem, visiblePredecessor = false;
-                               for (var i = 0; i < this._dropdownMenu.childElementCount; i++) {
-                                       listItem = this._dropdownMenu.children[i];
-                                       item = elData(listItem, 'item');
-                                       
-                                       if (item === 'divider') {
-                                               if (visiblePredecessor) {
-                                                       elShow(listItem);
-                                                       
-                                                       visiblePredecessor = false;
-                                               }
-                                               else {
-                                                       elHide(listItem);
-                                               }
-                                       }
-                                       else {
-                                               if (objOwns(visibility, item) && visibility[item] === false) {
-                                                       elHide(listItem);
-                                                       
-                                                       // check if previous item was a divider
-                                                       if (i > 0 && i + 1 === this._dropdownMenu.childElementCount) {
-                                                               if (elData(listItem.previousElementSibling, 'item') === 'divider') {
-                                                                       elHide(listItem.previousElementSibling);
-                                                               }
-                                                       }
-                                               }
-                                               else {
-                                                       elShow(listItem);
-                                                       
-                                                       visiblePredecessor = true;
-                                               }
-                                       }
-                               }
-                       }
-               },
-               
-               /**
-                * Returns the list of dropdown items for this type.
-                * 
-                * @return      {Array<Object>}         list of objects containing the type name and label
-                * @protected
-                */
-               _dropdownGetItems: function() {},
-               
-               /**
-                * Invoked once the dropdown for this type is shown, expects a list of type name and a boolean value
-                * to represent the visibility of each item. Items that do not appear in this list will be considered
-                * visible.
-                * 
-                * @return      {Object<string, boolean>}
-                * @protected
-                */
-               _dropdownOpen: function() {},
-               
-               /**
-                * Invoked whenever the user selects an item from the dropdown menu, the selected item is passed as argument.
-                * 
-                * @param       {string}        item    selected dropdown item
-                * @protected
-                */
-               _dropdownSelect: function(item) {},
-               
-               /**
-                * Handles clicks on a dropdown item.
-                * 
-                * @param       {Event}         event   event object
-                * @protected
-                */
-               _clickDropdownItem: function(event) {
-                       event.preventDefault();
-                       
-                       //noinspection JSCheckFunctionSignatures
-                       var item = elData(event.currentTarget, 'item');
-                       var data = {
-                               cancel: false,
-                               element: this._activeDropdownElement,
-                               item: item
-                       };
-                       EventHandler.fire('com.woltlab.wcf.inlineEditor', 'dropdownItemClick_' + this._options.dropdownIdentifier, data);
-                       
-                       if (data.cancel === true) {
-                               event.preventDefault();
-                       }
-                       else {
-                               this._dropdownSelect(item);
-                       }
-               },
-               
-               /**
-                * Prepares the message for editor display.
-                * 
-                * @protected
-                */
-               _prepare: function() {
-                       var data = this._elements.get(this._activeElement);
-                       
-                       var messageBodyEditor = elCreate('div');
-                       messageBodyEditor.className = 'messageBody editor';
-                       data.messageBodyEditor = messageBodyEditor;
-                       
-                       var icon = elCreate('span');
-                       icon.className = 'icon icon48 fa-spinner';
-                       messageBodyEditor.appendChild(icon);
-                       
-                       DomUtil.insertAfter(messageBodyEditor, data.messageBody);
-                       
-                       elHide(data.messageBody);
-               },
-               
-               /**
-                * Shows the message editor.
-                * 
-                * @param       {Object}        data            ajax response data
-                * @protected
-                */
-               _showEditor: function(data) {
-                       var id = this._getEditorId();
-                       var elementData = this._elements.get(this._activeElement);
-                       
-                       this._activeElement.classList.add('jsInvalidQuoteTarget');
-                       var icon = DomTraverse.childByClass(elementData.messageBodyEditor, 'icon');
-                       elRemove(icon);
-                       
-                       var messageBody = elementData.messageBodyEditor;
-                       var editor = elCreate('div');
-                       editor.className = 'editorContainer';
-                       //noinspection JSUnresolvedVariable
-                       DomUtil.setInnerHtml(editor, data.returnValues.template);
-                       messageBody.appendChild(editor);
-                       
-                       // bind buttons
-                       var formSubmit = elBySel('.formSubmit', editor);
-                       
-                       var buttonSave = elBySel('button[data-type="save"]', formSubmit);
-                       buttonSave.addEventListener(WCF_CLICK_EVENT, this._save.bind(this));
-                       
-                       var buttonCancel = elBySel('button[data-type="cancel"]', formSubmit);
-                       buttonCancel.addEventListener(WCF_CLICK_EVENT, this._restoreMessage.bind(this));
-                       
-                       EventHandler.add('com.woltlab.wcf.redactor', 'submitEditor_' + id, (function(data) {
-                               data.cancel = true;
-                               
-                               this._save();
-                       }).bind(this));
-                       
-                       // hide message header and footer
-                       elHide(elementData.messageHeader);
-                       elHide(elementData.messageFooter);
-                       
-                       var editorElement = elById(id);
-                       if (Environment.editor() === 'redactor') {
-                               window.setTimeout((function() {
-                                       if (this._options.quoteManager) {
-                                               this._options.quoteManager.setAlternativeEditor(id);
-                                       }
-                                       
-                                       UiScroll.element(this._activeElement);
-                               }).bind(this), 250);
-                       }
-                       else {
-                               editorElement.focus();
-                       }
-               },
-               
-               /**
-                * Restores the message view.
-                * 
-                * @protected
-                */
-               _restoreMessage: function() {
-                       var elementData = this._elements.get(this._activeElement);
-                       
-                       this._destroyEditor();
-                       
-                       elRemove(elementData.messageBodyEditor);
-                       elementData.messageBodyEditor = null;
-                       
-                       elShow(elementData.messageBody);
-                       elShow(elementData.messageFooter);
-                       elShow(elementData.messageHeader);
-                       this._activeElement.classList.remove('jsInvalidQuoteTarget');
-                       
-                       this._activeElement = null;
-                       
-                       if (this._options.quoteManager) {
-                               this._options.quoteManager.clearAlternativeEditor();
-                       }
-               },
-               
-               /**
-                * Saves the editor message.
-                * 
-                * @protected
-                */
-               _save: function() {
-                       var parameters = {
-                               containerID: this._options.containerId,
-                               data: {
-                                       message: ''
-                               },
-                               objectID: this._getObjectId(this._activeElement),
-                               removeQuoteIDs: (this._options.quoteManager) ? this._options.quoteManager.getQuotesMarkedForRemoval() : []
-                       };
-                       
-                       var id = this._getEditorId();
-                       
-                       // add any available settings
-                       var settingsContainer = elById('settings_' + id);
-                       if (settingsContainer) {
-                               elBySelAll('input, select, textarea', settingsContainer, function (element) {
-                                       if (element.nodeName === 'INPUT' && (element.type === 'checkbox' || element.type === 'radio')) {
-                                               if (!element.checked) {
-                                                       return;
-                                               }
-                                       }
-                                       
-                                       var name = element.name;
-                                       if (parameters.hasOwnProperty(name)) {
-                                               throw new Error("Variable overshadowing, key '" + name + "' is already present.");
-                                       }
-                                       
-                                       parameters[name] = element.value.trim();
-                               });
-                       }
-                       
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'getText_' + id, parameters.data);
-                       
-                       var validateResult = this._validate(parameters);
-                       
-                       if (!(validateResult instanceof Promise)) {
-                               if (validateResult === false) {
-                                       validateResult = Promise.reject();
-                               }
-                               else {
-                                       validateResult = Promise.resolve();
-                               }
-                       }
-                       
-                       validateResult.then(function () {
-                               EventHandler.fire('com.woltlab.wcf.redactor2', 'submit_' + id, parameters);
-                               
-                               Ajax.api(this, {
-                                       actionName: 'save',
-                                       parameters: parameters
-                               });
-                               
-                               this._hideEditor();
-                       }.bind(this), function(e) {
-                               console.log('Validation of post edit failed: '+ e);
-                       });
-               },
-               
-               /**
-                * Validates the message and invokes listeners to perform additional validation.
-                *
-                * @param       {Object}        parameters      request parameters
-                * @return      {boolean}       validation result
-                * @protected
-                */
-               _validate: function(parameters) {
-                       // remove all existing error elements
-                       elBySelAll('.innerError', this._activeElement, elRemove);
-                       
-                       var data = {
-                               api: this,
-                               parameters: parameters,
-                               valid: true,
-                               promises: []
-                       };
-                       
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'validate_' + this._getEditorId(), data);
-                       
-                       data.promises.push(Promise[data.valid ? 'resolve' : 'reject']());
-                       
-                       return Promise.all(data.promises);
-               },
-               
-               /**
-                * Throws an error by adding an inline error to target element.
-                *
-                * @param       {Element}       element         erroneous element
-                * @param       {string}        message         error message
-                */
-               throwError: function(element, message) {
-                       elInnerError(element, message);
-               },
-               
-               /**
-                * Shows the update message.
-                * 
-                * @param       {Object}        data            ajax response data
-                * @protected
-                */
-               _showMessage: function(data) {
-                       var activeElement = this._activeElement;
-                       var editorId = this._getEditorId();
-                       var elementData = this._elements.get(activeElement);
-                       var attachmentLists = elBySelAll('.attachmentThumbnailList, .attachmentFileList', elementData.messageFooter);
-                       
-                       // set new content
-                       //noinspection JSUnresolvedVariable
-                       DomUtil.setInnerHtml(DomTraverse.childByClass(elementData.messageBody, 'messageText'), data.returnValues.message);
-                       
-                       // handle attachment list
-                       //noinspection JSUnresolvedVariable
-                       if (typeof data.returnValues.attachmentList === 'string') {
-                               for (var i = 0, length = attachmentLists.length; i < length; i++) {
-                                       elRemove(attachmentLists[i]);
-                               }
-                               
-                               var element = elCreate('div');
-                               //noinspection JSUnresolvedVariable
-                               DomUtil.setInnerHtml(element, data.returnValues.attachmentList);
-                               
-                               var node;
-                               while (element.childNodes.length) {
-                                       node = element.childNodes[element.childNodes.length - 1];
-                                       elementData.messageFooter.insertBefore(node, elementData.messageFooter.firstChild);
-                               }
-                       }
-                       
-                       // handle poll
-                       //noinspection JSUnresolvedVariable
-                       if (typeof data.returnValues.poll === 'string') {
-                               // find current poll
-                               var poll = elBySel('.pollContainer', elementData.messageBody);
-                               if (poll !== null) {
-                                       // poll contain is wrapped inside `.jsInlineEditorHideContent`
-                                       elRemove(poll.parentNode);
-                               }
-                               
-                               var pollContainer = elCreate('div');
-                               pollContainer.className = 'jsInlineEditorHideContent';
-                               //noinspection JSUnresolvedVariable
-                               DomUtil.setInnerHtml(pollContainer, data.returnValues.poll);
-                               
-                               DomUtil.prepend(pollContainer, elementData.messageBody);
-                       }
-                       
-                       this._restoreMessage();
-                       
-                       this._updateHistory(this._getHash(this._getObjectId(activeElement)));
-                       
-                       EventHandler.fire('com.woltlab.wcf.redactor', 'autosaveDestroy_' + editorId);
-                       
-                       UiNotification.show();
-                       
-                       if (this._options.quoteManager) {
-                               this._options.quoteManager.clearAlternativeEditor();
-                               this._options.quoteManager.countQuotes();
-                       }
-               },
-               
-               /**
-                * Hides the editor from view.
-                * 
-                * @protected
-                */
-               _hideEditor: function() {
-                       var elementData = this._elements.get(this._activeElement);
-                       elHide(DomTraverse.childByClass(elementData.messageBodyEditor, 'editorContainer'));
-                       
-                       var icon = elCreate('span');
-                       icon.className = 'icon icon48 fa-spinner';
-                       elementData.messageBodyEditor.appendChild(icon);
-               },
-               
-               /**
-                * Restores the previously hidden editor.
-                * 
-                * @protected
-                */
-               _restoreEditor: function() {
-                       var elementData = this._elements.get(this._activeElement);
-                       var icon = elBySel('.fa-spinner', elementData.messageBodyEditor);
-                       elRemove(icon);
-                       
-                       var editorContainer = DomTraverse.childByClass(elementData.messageBodyEditor, 'editorContainer');
-                       if (editorContainer !== null) elShow(editorContainer);
-               },
-               
-               /**
-                * Destroys the editor instance.
-                * 
-                * @protected
-                */
-               _destroyEditor: function() {
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'autosaveDestroy_' + this._getEditorId());
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'destroy_' + this._getEditorId());
-               },
-               
-               /**
-                * Returns the hash added to the url after successfully editing a message.
-                * 
-                * @param       {int}   objectId        message object id
-                * @return      string
-                * @protected
-                */
-               _getHash: function(objectId) {
-                       return '#message' + objectId;
-               },
-               
-               /**
-                * Updates the history to avoid old content when going back in the browser
-                * history.
-                * 
-                * @param       {string}        hash    location hash
-                * @protected
-                */
-               _updateHistory: function(hash) {
-                       window.location.hash = hash;
-               },
-               
-               /**
-                * Returns the unique editor id.
-                * 
-                * @return      {string}        editor id
-                * @protected
-                */
-               _getEditorId: function() {
-                       return this._options.editorPrefix + this._getObjectId(this._activeElement);
-               },
-               
-               /**
-                * Returns the element's `data-object-id` value.
-                * 
-                * @param       {Element}       element         target element
-                * @return      {int}
-                * @protected
-                */
-               _getObjectId: function(element) {
-                       return ~~elData(element, 'object-id');
-               },
-               
-               _ajaxFailure: function(data) {
-                       var elementData = this._elements.get(this._activeElement);
-                       var editor = elBySel('.redactor-layer', elementData.messageBodyEditor);
-                       
-                       // handle errors occurring on editor load
-                       if (editor === null) {
-                               this._restoreMessage();
-                               
-                               return true;
-                       }
-                       
-                       this._restoreEditor();
-                       
-                       //noinspection JSUnresolvedVariable
-                       if (!data || data.returnValues === undefined || data.returnValues.realErrorMessage === undefined) {
-                               return true;
-                       }
-                       
-                       //noinspection JSUnresolvedVariable
-                       elInnerError(editor, data.returnValues.realErrorMessage);
-                       
-                       return false;
-               },
-               
-               _ajaxSuccess: function(data) {
-                       switch (data.actionName) {
-                               case 'beginEdit':
-                                       this._showEditor(data);
-                                       break;
-                                       
-                               case 'save':
-                                       this._showMessage(data);
-                                       break;
-                       }
-               },
-               
-               _ajaxSetup: function() {
-                       return {
-                               data: {
-                                       className: this._options.className,
-                                       interfaceName: 'wcf\\data\\IMessageInlineEditorAction'
-                               },
-                               silent: true
-                       };
-               },
-               
-               /** @deprecated 3.0 - used only for backward compatibility with `WCF.Message.InlineEditor` */
-               legacyEdit: function(containerId) {
-                       this._click(elById(containerId), null);
-               }
-       };
-       
-       return UiMessageInlineEditor;
-});
-
-/**
- * Provides access and editing of message properties.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Message/Manager
- */
-define('WoltLabSuite/Core/Ui/Message/Manager',['Ajax', 'Core', 'Dictionary', 'Language', 'Dom/ChangeListener', 'Dom/Util'], function(Ajax, Core, Dictionary, Language, DomChangeListener, DomUtil) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       rebuild: function() {},
-                       getPermission: function() {},
-                       getPropertyValue: function() {},
-                       update: function() {},
-                       updateItems: function() {},
-                       updateAllItems: function() {},
-                       setNote: function() {},
-                       _update: function() {},
-                       _updateState: function() {},
-                       _toggleMessageStatus: function() {},
-                       _getAttributeName: function() {},
-                       _ajaxSuccess: function() {},
-                       _ajaxSetup: function() {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * @param       {Object}        options         initialization options
-        * @constructor
-        */
-       function UiMessageManager(options) { this.init(options); }
-       UiMessageManager.prototype = {
-               /**
-                * Initializes a new manager instance.
-                * 
-                * @param       {Object}        options         initialization options
-                */
-               init: function(options) {
-                       this._elements = null;
-                       this._options = Core.extend({
-                               className: '',
-                               selector: ''
-                       }, options);
-                       
-                       this.rebuild();
-                       
-                       DomChangeListener.add('Ui/Message/Manager' + this._options.className, this.rebuild.bind(this));
-               },
-               
-               /**
-                * Rebuilds the list of observed messages. You should call this method whenever a
-                * message has been either added or removed from the document.
-                */
-               rebuild: function() {
-                       this._elements = new Dictionary();
-                       
-                       var element, elements = elBySelAll(this._options.selector);
-                       for (var i = 0, length = elements.length; i < length; i++) {
-                               element = elements[i];
-                               
-                               this._elements.set(elData(element, 'object-id'), element);
-                       }
-               },
-               
-               /**
-                * Returns a boolean value for the given permission. The permission should not start
-                * with "can" or "can-" as this is automatically assumed by this method.
-                * 
-                * @param       {int}           objectId        message object id 
-                * @param       {string}        permission      permission name without a leading "can" or "can-"
-                * @return      {boolean}       true if permission was set and is either 'true' or '1'
-                */
-               getPermission: function(objectId, permission) {
-                       permission = 'can-' + this._getAttributeName(permission);
-                       var element = this._elements.get(objectId);
-                       if (element === undefined) {
-                               throw new Error("Unknown object id '" + objectId + "' for selector '" + this._options.selector + "'");
-                       }
-                       
-                       return elDataBool(element, permission);
-               },
-               
-               /**
-                * Returns the given property value from a message, optionally supporting a boolean return value.
-                * 
-                * @param       {int}           objectId        message object id
-                * @param       {string}        propertyName    attribute name
-                * @param       {boolean}       asBool          attempt to interpret property value as boolean
-                * @return      {(boolean|string)}      raw property value or boolean if requested
-                */
-               getPropertyValue: function(objectId, propertyName, asBool) {
-                       var element = this._elements.get(objectId);
-                       if (element === undefined) {
-                               throw new Error("Unknown object id '" + objectId + "' for selector '" + this._options.selector + "'");
-                       }
-                       
-                       return window[(asBool ? 'elDataBool' : 'elData')](element, this._getAttributeName(propertyName));
-               },
-               
-               /**
-                * Invokes a method for given message object id in order to alter its state or properties.
-                * 
-                * @param       {int}           objectId        message object id
-                * @param       {string}        actionName      action name used for the ajax api
-                * @param       {Object=}       parameters      optional list of parameters included with the ajax request
-                */
-               update: function(objectId, actionName, parameters) {
-                       Ajax.api(this, {
-                               actionName: actionName,
-                               parameters: parameters || {},
-                               objectIDs: [objectId]
-                       });
-               },
-               
-               /**
-                * Updates properties and states for given object ids. Keep in mind that this method does
-                * not support setting individual properties per message, instead all property changes
-                * are applied to all matching message objects.
-                * 
-                * @param       {Array<int>}    objectIds       list of message object ids
-                * @param       {Object}        data            list of updated properties
-                */
-               updateItems: function(objectIds, data) {
-                       if (!Array.isArray(objectIds)) {
-                               objectIds = [objectIds];
-                       }
-                       
-                       var element;
-                       for (var i = 0, length = objectIds.length; i < length; i++) {
-                               element = this._elements.get(objectIds[i]);
-                               if (element === undefined) {
-                                       continue;
-                               }
-                               
-                               for (var key in data) {
-                                       if (data.hasOwnProperty(key)) {
-                                               this._update(element, key, data[key]);
-                                       }
-                               }
-                       }
-               },
-               
-               /**
-                * Bulk updates the properties and states for all observed messages at once.
-                * 
-                * @param       {Object}        data            list of updated properties
-                */
-               updateAllItems: function(data) {
-                       var objectIds = [];
-                       this._elements.forEach((function(element, objectId) {
-                               objectIds.push(objectId);
-                       }).bind(this));
-                       
-                       this.updateItems(objectIds, data);
-               },
-               
-               /**
-                * Sets or removes a message note identified by its unique CSS class.
-                * 
-                * @param       {int}           objectId        message object id
-                * @param       {string}        className       unique CSS class
-                * @param       {string}        htmlContent     HTML content
-                */
-               setNote: function (objectId, className, htmlContent) {
-                       var element = this._elements.get(objectId);
-                       if (element === undefined) {
-                               throw new Error("Unknown object id '" + objectId + "' for selector '" + this._options.selector + "'");
-                       }
-                       
-                       var messageFooterNotes = elBySel('.messageFooterNotes', element);
-                       var note = elBySel('.' + className, messageFooterNotes);
-                       if (htmlContent) {
-                               if (note === null) {
-                                       note = elCreate('p');
-                                       note.className = 'messageFooterNote ' + className;
-                                       
-                                       messageFooterNotes.appendChild(note);
-                               }
-                               
-                               note.innerHTML = htmlContent;
-                       }
-                       else if (note !== null) {
-                               elRemove(note);
-                       }
-               },
-               
-               /**
-                * Updates a single property of a message element.
-                * 
-                * @param       {Element}       element         message element
-                * @param       {string}        propertyName    property name
-                * @param       {?}             propertyValue   property value, will be implicitly converted to string
-                * @protected
-                */
-               _update: function(element, propertyName, propertyValue) {
-                       elData(element, this._getAttributeName(propertyName), propertyValue);
-                       
-                       // handle special properties
-                       var propertyValueBoolean = (propertyValue == 1 || propertyValue === true || propertyValue === 'true');
-                       this._updateState(element, propertyName, propertyValue, propertyValueBoolean);
-               },
-               
-               /**
-                * Updates the message element's state based upon a property change.
-                * 
-                * @param       {Element}       element                 message element
-                * @param       {string}        propertyName            property name
-                * @param       {?}             propertyValue           property value
-                * @param       {boolean}       propertyValueBoolean    true if `propertyValue` equals either 'true' or '1'
-                * @protected
-                */
-               _updateState: function(element, propertyName, propertyValue, propertyValueBoolean) {
-                       switch (propertyName) {
-                               case 'isDeleted':
-                                       element.classList[(propertyValueBoolean ? 'add' : 'remove')]('messageDeleted');
-                                       this._toggleMessageStatus(element, 'jsIconDeleted', 'wcf.message.status.deleted', 'red', propertyValueBoolean);
-                                       
-                                       break;
-                               
-                               case 'isDisabled':
-                                       element.classList[(propertyValueBoolean ? 'add' : 'remove')]('messageDisabled');
-                                       this._toggleMessageStatus(element, 'jsIconDisabled', 'wcf.message.status.disabled', 'green', propertyValueBoolean);
-                                       
-                                       break;
-                       }
-               },
-               
-               /**
-                * Toggles the message status bade for provided element.
-                * 
-                * @param       {Element}       element         message element
-                * @param       {string}        className       badge class name
-                * @param       {string}        phrase          language phrase
-                * @param       {string}        badgeColor      color css class
-                * @param       {boolean}       addBadge        add or remove badge
-                * @protected
-                */
-               _toggleMessageStatus: function(element, className, phrase, badgeColor, addBadge) {
-                       var messageStatus = elBySel('.messageStatus', element);
-                       if (messageStatus === null) {
-                               var messageHeaderMetaData = elBySel('.messageHeaderMetaData', element);
-                               if (messageHeaderMetaData === null) {
-                                       // can't find appropriate location to insert badge
-                                       return;
-                               }
-                               
-                               messageStatus = elCreate('ul');
-                               messageStatus.className = 'messageStatus';
-                               DomUtil.insertAfter(messageStatus, messageHeaderMetaData);
-                       }
-                       
-                       var badge = elBySel('.' + className, messageStatus);
-                       
-                       if (addBadge) {
-                               if (badge !== null) {
-                                       // badge already exists
-                                       return;
-                               }
-                               
-                               badge = elCreate('span');
-                               badge.className = 'badge label ' + badgeColor + ' ' + className;
-                               badge.textContent = Language.get(phrase);
-                               
-                               var listItem = elCreate('li');
-                               listItem.appendChild(badge);
-                               messageStatus.appendChild(listItem);
-                       }
-                       else {
-                               if (badge === null) {
-                                       // badge does not exist
-                                       return;
-                               }
-                               
-                               elRemove(badge.parentNode);
-                       }
-               },
-               
-               /**
-                * Transforms camel-cased property names into their attribute equivalent.
-                * 
-                * @param       {string}        propertyName    camel-cased property name
-                * @return      {string}        equivalent attribute name
-                * @protected
-                */
-               _getAttributeName: function(propertyName) {
-                       if (propertyName.indexOf('-') !== -1) {
-                               return propertyName;
-                       }
-                       
-                       var attributeName = '';
-                       var str, tmp = propertyName.split(/([A-Z][a-z]+)/);
-                       for (var i = 0, length = tmp.length; i < length; i++) {
-                               str = tmp[i];
-                               if (str.length) {
-                                       if (attributeName.length) attributeName += '-';
-                                       attributeName += str.toLowerCase();
-                               }
-                       }
-                       
-                       return attributeName;
-               },
-               
-               _ajaxSuccess: function() {
-                       throw new Error("Method _ajaxSuccess() must be implemented by deriving functions.");
-               },
-               
-               _ajaxSetup: function() {
-                       return {
-                               data: {
-                                       className: this._options.className
-                               }
-                       };
-               }
-       };
-       
-       return UiMessageManager;
-});
-/**
- * Handles user interaction with the quick reply feature.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Message/Reply
- */
-define('WoltLabSuite/Core/Ui/Message/Reply',['Ajax', 'Core', 'EventHandler', 'Language', 'Dom/ChangeListener', 'Dom/Util', 'Dom/Traverse', 'Ui/Dialog', 'Ui/Notification', 'WoltLabSuite/Core/Ui/Scroll', 'EventKey', 'User', 'WoltLabSuite/Core/Controller/Captcha'],
-       function(Ajax, Core, EventHandler, Language, DomChangeListener, DomUtil, DomTraverse, UiDialog, UiNotification, UiScroll, EventKey, User, ControllerCaptcha) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _submitGuestDialog: function() {},
-                       _submit: function() {},
-                       _validate: function() {},
-                       throwError: function() {},
-                       _showLoadingOverlay: function() {},
-                       _hideLoadingOverlay: function() {},
-                       _reset: function() {},
-                       _handleError: function() {},
-                       _getEditor: function() {},
-                       _insertMessage: function() {},
-                       _ajaxSuccess: function() {},
-                       _ajaxFailure: function() {},
-                       _ajaxSetup: function() {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * @constructor
-        */
-       function UiMessageReply(options) { this.init(options); }
-       UiMessageReply.prototype = {
-               /**
-                * Initializes a new quick reply field.
-                * 
-                * @param       {Object}        options         configuration options
-                */
-               init: function(options) {
-                       this._options = Core.extend({
-                               ajax: {
-                                       className: ''
-                               },
-                               quoteManager: null,
-                               successMessage: 'wcf.global.success.add'
-                       }, options);
-                       
-                       this._container = elById('messageQuickReply');
-                       this._content = elBySel('.messageContent', this._container);
-                       this._textarea = elById('text');
-                       this._editor = null;
-                       this._guestDialogId = '';
-                       this._loadingOverlay = null;
-                       
-                       // prevent marking of text for quoting
-                       elBySel('.message', this._container).classList.add('jsInvalidQuoteTarget');
-                       
-                       // handle submit button
-                       var submitCallback = this._submit.bind(this);
-                       var submitButton = elBySel('button[data-type="save"]', this._container);
-                       submitButton.addEventListener(WCF_CLICK_EVENT, submitCallback);
-                       
-                       // bind reply button
-                       var replyButtons = elBySelAll('.jsQuickReply');
-                       for (var i = 0, length = replyButtons.length; i < length; i++) {
-                               replyButtons[i].addEventListener(WCF_CLICK_EVENT, (function(event) {
-                                       event.preventDefault();
-                                       
-                                       this._getEditor().WoltLabReply.showEditor();
-                                       
-                                       UiScroll.element(this._container, (function() {
-                                               this._getEditor().WoltLabCaret.endOfEditor();
-                                       }).bind(this));
-                               }).bind(this));
-                       }
-               },
-               
-               /**
-                * Submits the guest dialog.
-                * 
-                * @param       {Event}         event
-                * @protected
-                */
-               _submitGuestDialog: function(event) {
-                       // only submit when enter key is pressed
-                       if (event.type === 'keypress' && !EventKey.Enter(event)) {
-                               return;
-                       }
-                       
-                       var usernameInput = elBySel('input[name=username]', event.currentTarget.closest('.dialogContent'));
-                       if (usernameInput.value === '') {
-                               elInnerError(usernameInput, Language.get('wcf.global.form.error.empty'));
-                               usernameInput.closest('dl').classList.add('formError');
-                               
-                               return;
-                       }
-                       
-                       var parameters = {
-                               parameters: {
-                                       data: {
-                                               username: usernameInput.value
-                                       }
-                               }
-                       };
-                       
-                       //noinspection JSCheckFunctionSignatures
-                       var captchaId = elData(event.currentTarget, 'captcha-id');
-                       if (ControllerCaptcha.has(captchaId)) {
-                               var data = ControllerCaptcha.getData(captchaId);
-                               if (data instanceof Promise) {
-                                       data.then((function (data) {
-                                               parameters = Core.extend(parameters, data);
-                                               this._submit(undefined, parameters);
-                                       }).bind(this));
-                               }
-                               else {
-                                       parameters = Core.extend(parameters, ControllerCaptcha.getData(captchaId));
-                                       this._submit(undefined, parameters);
-                               }
-                       }
-                       else {
-                               this._submit(undefined, parameters);
-                       }
-               },
-               
-               /**
-                * Validates the message and submits it to the server.
-                * 
-                * @param       {Event?}        event                   event object
-                * @param       {Object?}       additionalParameters    additional parameters sent to the server
-                * @protected
-                */
-               _submit: function(event, additionalParameters) {
-                       if (event) {
-                               event.preventDefault();
-                       }
-                       
-                       // Ignore requests to submit the message while a previous request is still pending.
-                       if (this._content.classList.contains('loading')) {
-                               if (!this._guestDialogId || !UiDialog.isOpen(this._guestDialogId)) {
-                                       return;
-                               }
-                       }
-                       
-                       if (!this._validate()) {
-                               // validation failed, bail out
-                               return;
-                       }
-                       
-                       this._showLoadingOverlay();
-                       
-                       // build parameters
-                       var parameters = DomUtil.getDataAttributes(this._container, 'data-', true, true);
-                       parameters.data = { message: this._getEditor().code.get() };
-                       parameters.removeQuoteIDs = (this._options.quoteManager) ? this._options.quoteManager.getQuotesMarkedForRemoval() : [];
-                       
-                       // add any available settings
-                       var settingsContainer = elById('settings_text');
-                       if (settingsContainer) {
-                               elBySelAll('input, select, textarea', settingsContainer, function (element) {
-                                       if (element.nodeName === 'INPUT' && (element.type === 'checkbox' || element.type === 'radio')) {
-                                               if (!element.checked) {
-                                                       return;
-                                               }
-                                       }
-                                       
-                                       var name = element.name;
-                                       if (parameters.hasOwnProperty(name)) {
-                                               throw new Error("Variable overshadowing, key '" + name + "' is already present.");
-                                       }
-                                       
-                                       parameters[name] = element.value.trim();
-                               });
-                       }
-                       
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'submit_text', parameters.data);
-                       
-                       if (!User.userId && !additionalParameters) {
-                               parameters.requireGuestDialog = true;
-                       }
-                       
-                       Ajax.api(this, Core.extend({
-                               parameters: parameters
-                       }, additionalParameters));
-               },
-               
-               /**
-                * Validates the message and invokes listeners to perform additional validation.
-                * 
-                * @return      {boolean}       validation result
-                * @protected
-                */
-               _validate: function() {
-                       // remove all existing error elements
-                       elBySelAll('.innerError', this._container, elRemove);
-                       
-                       // check if editor contains actual content
-                       if (this._getEditor().utils.isEmpty()) {
-                               this.throwError(this._textarea, Language.get('wcf.global.form.error.empty'));
-                               return false;
-                       }
-                       
-                       var data = {
-                               api: this,
-                               editor: this._getEditor(),
-                               message: this._getEditor().code.get(),
-                               valid: true
-                       };
-                       
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'validate_text', data);
-                       
-                       return (data.valid !== false);
-               },
-               
-               /**
-                * Throws an error by adding an inline error to target element.
-                * 
-                * @param       {Element}       element         erroneous element
-                * @param       {string}        message         error message
-                */
-               throwError: function(element, message) {
-                       elInnerError(element, (message === 'empty' ? Language.get('wcf.global.form.error.empty') : message));
-               },
-               
-               /**
-                * Displays a loading spinner while the request is processed by the server.
-                * 
-                * @protected
-                */
-               _showLoadingOverlay: function() {
-                       if (this._loadingOverlay === null) {
-                               this._loadingOverlay = elCreate('div');
-                               this._loadingOverlay.className = 'messageContentLoadingOverlay';
-                               this._loadingOverlay.innerHTML = '<span class="icon icon96 fa-spinner"></span>';
-                       }
-                       
-                       this._content.classList.add('loading');
-                       this._content.appendChild(this._loadingOverlay);
-               },
-               
-               /**
-                * Hides the loading spinner.
-                * 
-                * @protected
-                */
-               _hideLoadingOverlay: function() {
-                       this._content.classList.remove('loading');
-                       
-                       var loadingOverlay = elBySel('.messageContentLoadingOverlay', this._content);
-                       if (loadingOverlay !== null) {
-                               loadingOverlay.parentNode.removeChild(loadingOverlay);
-                       }
-               },
-               
-               /**
-                * Resets the editor contents and notifies event listeners.
-                * 
-                * @protected
-                */
-               _reset: function() {
-                       this._getEditor().code.set('<p>\u200b</p>');
-                       
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'reset_text');
-               },
-               
-               /**
-                * Handles errors occurred during server processing.
-                * 
-                * @param       {Object}        data    response data
-                * @protected
-                */
-               _handleError: function(data) {
-                       var parameters = {
-                               api: this,
-                               cancel: false,
-                               returnValues: data.returnValues
-                       };
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'handleError_text', parameters);
-                       
-                       if (parameters.cancel !== true) {
-                               //noinspection JSUnresolvedVariable
-                               this.throwError(this._textarea, data.returnValues.realErrorMessage);
-                       }
-               },
-               
-               /**
-                * Returns the current editor instance.
-                * 
-                * @return      {Object}       editor instance
-                * @protected
-                */
-               _getEditor: function() {
-                       if (this._editor === null) {
-                               if (typeof window.jQuery === 'function') {
-                                       this._editor = window.jQuery(this._textarea).data('redactor');
-                               }
-                               else {
-                                       throw new Error("Unable to access editor, jQuery has not been loaded yet.");
-                               }
-                       }
-                       
-                       return this._editor;
-               },
-               
-               /**
-                * Inserts the rendered message into the post list, unless the post is on the next
-                * page in which case a redirect will be performed instead.
-                * 
-                * @param       {Object}        data    response data
-                * @protected
-                */
-               _insertMessage: function(data) {
-                       this._getEditor().WoltLabAutosave.reset();
-                       
-                       // redirect to new page
-                       //noinspection JSUnresolvedVariable
-                       if (data.returnValues.url) {
-                               //noinspection JSUnresolvedVariable
-                               if (window.location == data.returnValues.url) {
-                                       window.location.reload();
-                               }
-                               window.location = data.returnValues.url;
-                       }
-                       else {
-                               //noinspection JSUnresolvedVariable
-                               if (data.returnValues.template) {
-                                       var elementId;
-                                       
-                                       // insert HTML
-                                       if (elData(this._container, 'sort-order') === 'DESC') {
-                                               //noinspection JSUnresolvedVariable
-                                               DomUtil.insertHtml(data.returnValues.template, this._container, 'after');
-                                               elementId = DomUtil.identify(this._container.nextElementSibling);
-                                       }
-                                       else {
-                                               var insertBefore = this._container;
-                                               if (insertBefore.previousElementSibling && insertBefore.previousElementSibling.classList.contains('messageListPagination')) {
-                                                       insertBefore = insertBefore.previousElementSibling;
-                                               }
-                                               
-                                               //noinspection JSUnresolvedVariable
-                                               DomUtil.insertHtml(data.returnValues.template, insertBefore, 'before');
-                                               elementId = DomUtil.identify(insertBefore.previousElementSibling);
-                                       }
-                                       
-                                       // update last post time
-                                       //noinspection JSUnresolvedVariable
-                                       elData(this._container, 'last-post-time', data.returnValues.lastPostTime);
-                                       
-                                       window.history.replaceState(undefined, '', '#' + elementId);
-                                       UiScroll.element(elById(elementId));
-                               }
-                               
-                               UiNotification.show(Language.get(this._options.successMessage));
-                               
-                               if (this._options.quoteManager) {
-                                       this._options.quoteManager.countQuotes();
-                               }
-                               
-                               DomChangeListener.trigger();
-                       }
-               },
-               
-               /**
-                * @param {{returnValues:{guestDialog:string,guestDialogID:string}}} data
-                * @protected
-                */
-               _ajaxSuccess: function(data) {
-                       if (!User.userId && !data.returnValues.guestDialogID) {
-                               throw new Error("Missing 'guestDialogID' return value for guest.");
-                       }
-                       
-                       if (!User.userId && data.returnValues.guestDialog) {
-                               UiDialog.openStatic(data.returnValues.guestDialogID, data.returnValues.guestDialog, {
-                                       closable: false,
-                                       onClose: function() {
-                                               if (ControllerCaptcha.has(data.returnValues.guestDialogID)) {
-                                                       ControllerCaptcha.delete(data.returnValues.guestDialogID);
-                                               }
-                                       },
-                                       title: Language.get('wcf.global.confirmation.title')
-                               });
-                               
-                               var dialog = UiDialog.getDialog(data.returnValues.guestDialogID);
-                               elBySel('input[type=submit]', dialog.content).addEventListener(WCF_CLICK_EVENT, this._submitGuestDialog.bind(this));
-                               elBySel('input[type=text]', dialog.content).addEventListener('keypress', this._submitGuestDialog.bind(this));
-                               
-                               this._guestDialogId = data.returnValues.guestDialogID;
-                       }
-                       else {
-                               this._insertMessage(data);
-                               
-                               if (!User.userId) {
-                                       UiDialog.close(data.returnValues.guestDialogID);
-                               }
-                               
-                               this._reset();
-                               
-                               this._hideLoadingOverlay();
-                       }
-               },
-               
-               _ajaxFailure: function(data) {
-                       this._hideLoadingOverlay();
-                       
-                       //noinspection JSUnresolvedVariable
-                       if (data === null || data.returnValues === undefined || data.returnValues.realErrorMessage === undefined) {
-                               return true;
-                       }
-                       
-                       this._handleError(data);
-                       
-                       return false;
-               },
-               
-               _ajaxSetup: function() {
-                       return {
-                               data: {
-                                       actionName: 'quickReply',
-                                       className: this._options.ajax.className,
-                                       interfaceName: 'wcf\\data\\IMessageQuickReplyAction'
-                               },
-                               silent: true
-                       };
-               }
-       };
-       
-       return UiMessageReply;
-});
-
-/**
- * Provides buttons to share a page through multiple social community sites.
- *
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Message/Share
- */
-define('WoltLabSuite/Core/Ui/Message/Share',['EventHandler', 'StringUtil'], function(EventHandler, StringUtil) {
-       "use strict";
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Message/Share
-        */
-       return {
-               _pageDescription: '',
-               _pageUrl: '',
-               
-               init: function() {
-                       var title = elBySel('meta[property="og:title"]');
-                       if (title !== null) this._pageDescription = encodeURIComponent(title.content);
-                       var url = elBySel('meta[property="og:url"]');
-                       if (url !== null) this._pageUrl = encodeURIComponent(url.content);
-                       
-                       elBySelAll('.jsMessageShareButtons', null, (function(container) {
-                               container.classList.remove('jsMessageShareButtons');
-                               
-                               var pageUrl = encodeURIComponent(StringUtil.unescapeHTML(elData(container, 'url') || ''));
-                               if (!pageUrl) {
-                                       pageUrl = this._pageUrl;
-                               }
-                               
-                               var providers = {
-                                       facebook: {
-                                               link: elBySel('.jsShareFacebook', container),
-                                               share: (function(event) { this._share('facebook', 'https://www.facebook.com/sharer.php?u={pageURL}&t={text}', true, pageUrl); }).bind(this)
-                                       },
-                                       google: {
-                                               link: elBySel('.jsShareGoogle', container),
-                                               share: (function(event) { this._share('google', 'https://plus.google.com/share?url={pageURL}', false, pageUrl); }).bind(this)
-                                       },
-                                       reddit: {
-                                               link: elBySel('.jsShareReddit', container),
-                                               share: (function(event) { this._share('reddit', 'https://ssl.reddit.com/submit?url={pageURL}', false, pageUrl); }).bind(this)
-                                       },
-                                       twitter: {
-                                               link: elBySel('.jsShareTwitter', container),
-                                               share: (function(event) { this._share('twitter', 'https://twitter.com/share?url={pageURL}&text={text}', false, pageUrl); }).bind(this)
-                                       },
-                                       linkedIn: {
-                                               link: elBySel('.jsShareLinkedIn', container),
-                                               share: (function(event) { this._share('linkedIn', 'https://www.linkedin.com/cws/share?url={pageURL}', false, pageUrl); }).bind(this)
-                                       },
-                                       pinterest: {
-                                               link: elBySel('.jsSharePinterest', container),
-                                               share: (function(event) { this._share('pinterest', 'https://www.pinterest.com/pin/create/link/?url={pageURL}&description={text}', false, pageUrl); }).bind(this)
-                                       },
-                                       xing: {
-                                               link: elBySel('.jsShareXing', container),
-                                               share: (function(event) { this._share('xing', 'https://www.xing.com/social_plugins/share?url={pageURL}', false, pageUrl); }).bind(this)
-                                       },
-                                       whatsApp: {
-                                               link: elBySel('.jsShareWhatsApp', container),
-                                               share: (function() {
-                                                       window.location.href = 'whatsapp://send?text=' + this._pageDescription + '%20' + pageUrl;
-                                               }).bind(this)
-                                       }
-                               };
-                               
-                               EventHandler.fire('com.woltlab.wcf.message.share', 'shareProvider', {
-                                       container: container,
-                                       providers: providers,
-                                       pageDescription: this._pageDescription,
-                                       pageUrl: this._pageUrl
-                               });
-                               
-                               for (var provider in providers) {
-                                       if (providers.hasOwnProperty(provider)) {
-                                               if (providers[provider].link !== null) {
-                                                       providers[provider].link.addEventListener(WCF_CLICK_EVENT, providers[provider].share);
-                                               }
-                                       }
-                               }
-                       }).bind(this));
-               },
-               
-               _share: function(objectName, url, appendUrl, pageUrl) {
-                       // fallback for plugins
-                       if (!pageUrl) {
-                               pageUrl = this._pageUrl;
-                       }
-                       
-                       window.open(
-                               url.replace(/\{pageURL}/, pageUrl).replace(/\{text}/, this._pageDescription + (appendUrl ? "%20" + pageUrl : "")),
-                               objectName,
-                               'height=600,width=600'
-                       );
-               }
-       };
-});
-
-define('WoltLabSuite/Core/Ui/Page/Search',['Ajax', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog'], function(Ajax, EventKey, Language, StringUtil, DomUtil, UiDialog) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       open: function() {},
-                       _search: function() {},
-                       _click: function() {},
-                       _ajaxSuccess: function() {},
-                       _ajaxSetup: function() {},
-                       _dialogSetup: function() {}
-               };
-               return Fake;
-       }
-       
-       var _callbackSelect, _resultContainer, _resultList, _searchInput = null;
-       
-       return {
-               open: function(callbackSelect) {
-                       _callbackSelect = callbackSelect;
-                       
-                       UiDialog.open(this);
-               },
-               
-               _search: function (event) {
-                       event.preventDefault();
-                       
-                       var inputContainer = _searchInput.parentNode;
-                       
-                       var value = _searchInput.value.trim();
-                       if (value.length < 3) {
-                               elInnerError(inputContainer, Language.get('wcf.page.search.error.tooShort'));
-                               return;
-                       }
-                       else {
-                               elInnerError(inputContainer, false);
-                       }
-                       
-                       Ajax.api(this, {
-                               parameters: {
-                                       searchString: value
-                               }
-                       });
-               },
-               
-               _click: function (event) {
-                       event.preventDefault();
-                       
-                       var page = event.currentTarget;
-                       var pageTitle = elBySel('h3', page).textContent.replace(/['"]/g, '');
-                       
-                       _callbackSelect(elData(page, 'page-id') + '#' + pageTitle);
-                       
-                       UiDialog.close(this);
-               },
-               
-               _ajaxSuccess: function(data) {
-                       var html = '', page;
-                       //noinspection JSUnresolvedVariable
-                       for (var i = 0, length = data.returnValues.length; i < length; i++) {
-                               //noinspection JSUnresolvedVariable
-                               page = data.returnValues[i];
-                               
-                               html += '<li>'
-                                               + '<div class="containerHeadline pointer" data-page-id="' + page.pageID + '">'
-                                                       + '<h3>' + StringUtil.escapeHTML(page.name) + '</h3>'
-                                                       + '<small>' + StringUtil.escapeHTML(page.displayLink) + '</small>'
-                                               + '</div>'
-                                       + '</li>';
-                       }
-                       
-                       _resultList.innerHTML = html;
-                       
-                       window[html ? 'elShow' : 'elHide'](_resultContainer);
-                       
-                       if (html) {
-                               elBySelAll('.containerHeadline', _resultList, (function(item) {
-                                       item.addEventListener(WCF_CLICK_EVENT, this._click.bind(this));
-                               }).bind(this));
-                       }
-                       else {
-                               elInnerError(_searchInput.parentNode, Language.get('wcf.page.search.error.noResults'));
-                       }
-               },
-               
-               _ajaxSetup: function () {
-                       return {
-                               data: {
-                                       actionName: 'search',
-                                       className: 'wcf\\data\\page\\PageAction'
-                               }
-                       };
-               },
-               
-               _dialogSetup: function() {
-                       return {
-                               id: 'wcfUiPageSearch',
-                               options: {
-                                       onSetup: (function() {
-                                               var callbackSearch = this._search.bind(this);
-                                               
-                                               _searchInput = elById('wcfUiPageSearchInput');
-                                               _searchInput.addEventListener('keydown', function(event) {
-                                                       if (EventKey.Enter(event)) {
-                                                               callbackSearch(event);
-                                                       }
-                                               });
-                                               
-                                               _searchInput.nextElementSibling.addEventListener(WCF_CLICK_EVENT, callbackSearch);
-                                               
-                                               _resultContainer = elById('wcfUiPageSearchResultContainer');
-                                               _resultList = elById('wcfUiPageSearchResultList');
-                                       }).bind(this),
-                                       onShow: function() {
-                                               _searchInput.focus();
-                                       },
-                                       title: Language.get('wcf.page.search')
-                               },
-                               source: '<div class="section">'
-                                       + '<dl>'
-                                               + '<dt><label for="wcfUiPageSearchInput">' + Language.get('wcf.page.search.name') + '</label></dt>'
-                                               + '<dd>'
-                                                       + '<div class="inputAddon">'
-                                                               + '<input type="text" id="wcfUiPageSearchInput" class="long">'
-                                                               + '<a href="#" class="inputSuffix"><span class="icon icon16 fa-search"></span></a>'
-                                                       + '</div>'
-                                               + '</dd>'
-                                       + '</dl>'
-                               + '</div>'
-                               + '<section id="wcfUiPageSearchResultContainer" class="section" style="display: none;">'
-                                       + '<header class="sectionHeader">'
-                                               + '<h2 class="sectionTitle">' + Language.get('wcf.page.search.results') + '</h2>'
-                                       + '</header>'
-                                       + '<ol id="wcfUiPageSearchResultList" class="containerList"></ol>'
-                               + '</section>'
-                       };
-               }
-       };
-});
-
-/**
- * Sortable lists with optimized handling per device sizes.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Sortable/List
- */
-define('WoltLabSuite/Core/Ui/Sortable/List',['Core', 'Ui/Screen'], function (Core, UiScreen) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _enable: function() {},
-                       _disable: function() {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * @constructor
-        */
-       function UiSortableList(options) { this.init(options); }
-       UiSortableList.prototype = {
-               /**
-                * Initializes the sortable list controller.
-                * 
-                * @param       {Object}        options         initialization options for `WCF.Sortable.List`
-                */
-               init: function (options) {
-                       this._options = Core.extend({
-                               containerId: '',
-                               className: '',
-                               offset: 0,
-                               options: {},
-                               isSimpleSorting: false,
-                               additionalParameters: {}
-                       }, options);
-                       
-                       UiScreen.on('screen-sm-md', {
-                               match: this._enable.bind(this, true),
-                               unmatch: this._disable.bind(this),
-                               setup: this._enable.bind(this, true)
-                       });
-                       
-                       UiScreen.on('screen-lg', {
-                               match: this._enable.bind(this, false),
-                               unmatch: this._disable.bind(this),
-                               setup: this._enable.bind(this, false)
-                       });
-               },
-               
-               /**
-                * Enables sorting with an optional sort handle.
-                * 
-                * @param       {boolean}       hasHandle       true if sort can only be started with the sort handle
-                * @protected
-                */
-               _enable: function (hasHandle) {
-                       var options = this._options.options;
-                       if (hasHandle) options.handle = '.sortableNodeHandle';
-                       
-                       new window.WCF.Sortable.List(
-                               this._options.containerId,
-                               this._options.className,
-                               this._options.offset,
-                               options,
-                               this._options.isSimpleSorting,
-                               this._options.additionalParameters
-                       );
-               },
-               
-               /**
-                * Disables sorting for registered containers.
-                * 
-                * @protected
-                */
-               _disable: function () {
-                       window.jQuery('#' + this._options.containerId + ' .sortableList')[(this._options.isSimpleSorting ? 'sortable' : 'nestedSortable')]('destroy');
-               }
-       };
-       
-       return UiSortableList;
-});
-/**
- * Handles the data to create and edit a poll in a form created via form builder.
- * 
- * @author     Alexander Ebert, Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Poll/Editor
- * @since      5.2
- */
-define('WoltLabSuite/Core/Ui/Poll/Editor',[
-       'Core',
-       'Dom/Util',
-       'EventHandler',
-       'EventKey',
-       'Language',
-       'WoltLabSuite/Core/Date/Picker',
-       'WoltLabSuite/Core/Ui/Sortable/List'
-], function(
-       Core,
-       DomUtil,
-       EventHandler,
-       EventKey,
-       Language,
-       DatePicker,
-       UiSortableList
-) {
-       "use strict";
-       
-       function UiPollEditor(containerId, pollOptions, wysiwygId, options) {
-               this.init(containerId, pollOptions, wysiwygId, options);
-       }
-       UiPollEditor.prototype = {
-               /**
-                * Initializes the poll editor.
-                * 
-                * @param       {string}        containerId     id of the poll options container
-                * @param       {object[]}      pollOptions     existing poll options
-                * @param       {string}        wysiwygId       id of the related wysiwyg editor
-                * @param       {object}        options         additional poll options
-                */
-               init: function(containerId, pollOptions, wysiwygId, options) {
-                       this._container = elById(containerId);
-                       if (this._container === null) {
-                               throw new Error("Unknown poll editor container with id '" + containerId + "'.");
-                       }
-                       
-                       this._wysiwygId = wysiwygId;
-                       if (wysiwygId !== '' && elById(wysiwygId) === null) {
-                               throw new Error("Unknown wysiwyg field with id '" + wysiwygId + "'.");
-                       }
-                       
-                       this.questionField = elById(this._wysiwygId + 'Poll_question');
-                       
-                       var optionLists = elByClass('sortableList', this._container);
-                       if (optionLists.length === 0) {
-                               throw new Error("Cannot find poll options list for container with id '" + containerId + "'.");
-                       }
-                       this.optionList = optionLists[0];
-                       
-                       this.endTimeField = elById(this._wysiwygId + 'Poll_endTime');
-                       this.maxVotesField = elById(this._wysiwygId + 'Poll_maxVotes');
-                       this.isChangeableYesField = elById(this._wysiwygId + 'Poll_isChangeable');
-                       this.isChangeableNoField = elById(this._wysiwygId + 'Poll_isChangeable_no');
-                       this.isPublicYesField = elById(this._wysiwygId + 'Poll_isPublic');
-                       this.isPublicNoField = elById(this._wysiwygId + 'Poll_isPublic_no');
-                       this.resultsRequireVoteYesField = elById(this._wysiwygId + 'Poll_resultsRequireVote');
-                       this.resultsRequireVoteNoField = elById(this._wysiwygId + 'Poll_resultsRequireVote_no');
-                       this.sortByVotesYesField = elById(this._wysiwygId + 'Poll_sortByVotes');
-                       this.sortByVotesNoField = elById(this._wysiwygId + 'Poll_sortByVotes_no');
-                       
-                       this._optionCount = 0;
-                       this._options = Core.extend({
-                               isAjax: false,
-                               maxOptions: 20
-                       }, options);
-                       
-                       this._createOptionList(pollOptions || []);
-                       
-                       new UiSortableList({
-                               containerId: containerId,
-                               options: {
-                                       toleranceElement: '> div'
-                               }
-                       });
-                       
-                       if (this._options.isAjax) {
-                               var events = ['handleError', 'reset', 'submit', 'validate'];
-                               for (var i = 0, length = events.length; i < length; i++) {
-                                       var event = events[i];
-                                       
-                                       EventHandler.add(
-                                               'com.woltlab.wcf.redactor2',
-                                               event + '_' + this._wysiwygId,
-                                               this['_' + event].bind(this)
-                                       );
-                               }
-                       }
-                       else {
-                               var form = this._container.closest('form');
-                               if (form === null) {
-                                       throw new Error("Cannot find form for container with id '" + containerId + "'.");
-                               }
-                               
-                               form.addEventListener('submit', this._submit.bind(this));
-                       }
-               },
-               
-               /**
-                * Adds an option based on below the option for which the `Add Option` button has
-                * been clicked.
-                * 
-                * @param       {Event}         event           icon click event
-                */
-               _addOption: function(event) {
-                       event.preventDefault();
-                       
-                       if (this._optionCount === this._options.maxOptions) {
-                               return false;
-                       }
-                       
-                       this._createOption(
-                               undefined,
-                               undefined,
-                               event.currentTarget.closest('li')
-                       );
-               },
-               
-               /**
-                * Creates a new option based on the given data or an empty option if no option data
-                * is given.
-                * 
-                * @param       {string}        optionValue     value of the option
-                * @param       {integer}       optionId        id of the option
-                * @param       {Element?}      insertAfter     optional element after which the new option is added
-                * @private
-                */
-               _createOption: function(optionValue, optionId, insertAfter) {
-                       optionValue = optionValue || '';
-                       optionId = ~~optionId || 0;
-                       
-                       var listItem = elCreate('LI');
-                       listItem.className = 'sortableNode';
-                       elData(listItem, 'option-id', optionId);
-                       
-                       if (insertAfter) {
-                               DomUtil.insertAfter(listItem, insertAfter);
-                       }
-                       else {
-                               this.optionList.appendChild(listItem);
-                       }
-                       
-                       var pollOptionInput = elCreate('div');
-                       pollOptionInput.className = 'pollOptionInput';
-                       listItem.appendChild(pollOptionInput);
-                       
-                       var sortHandle = elCreate('span');
-                       sortHandle.className = 'icon icon16 fa-arrows sortableNodeHandle';
-                       pollOptionInput.appendChild(sortHandle);
-                       
-                       // buttons
-                       var addButton = elCreate('a');
-                       elAttr(addButton, 'role', 'button');
-                       elAttr(addButton, 'href', '#');
-                       addButton.className = 'icon icon16 fa-plus jsTooltip jsAddOption pointer';
-                       elAttr(addButton, 'title', Language.get('wcf.poll.button.addOption'));
-                       addButton.addEventListener('click', this._addOption.bind(this));
-                       pollOptionInput.appendChild(addButton);
-                       
-                       var deleteButton = elCreate('a');
-                       elAttr(deleteButton, 'role', 'button');
-                       elAttr(deleteButton, 'href', '#');
-                       deleteButton.className = 'icon icon16 fa-times jsTooltip jsDeleteOption pointer';
-                       elAttr(deleteButton, 'title', Language.get('wcf.poll.button.removeOption'));
-                       deleteButton.addEventListener('click', this._removeOption.bind(this));
-                       pollOptionInput.appendChild(deleteButton);
-                       
-                       // input field
-                       var optionInput = elCreate('input');
-                       elAttr(optionInput, 'type', 'text');
-                       optionInput.value = optionValue;
-                       elAttr(optionInput, 'maxlength', 255);
-                       optionInput.addEventListener('keydown', this._optionInputKeyDown.bind(this));
-                       optionInput.addEventListener('click', function() {
-                               // work-around for some weird focus issue on iOS/Android
-                               if (document.activeElement !== this) {
-                                       this.focus();
-                               }
-                       });
-                       pollOptionInput.appendChild(optionInput);
-                       
-                       if (insertAfter !== null) {
-                               optionInput.focus();
-                       }
-                       
-                       this._optionCount++;
-                       if (this._optionCount === this._options.maxOptions) {
-                               elBySelAll('span.jsAddOption', this.optionList, function(icon) {
-                                       icon.classList.remove('pointer');
-                                       icon.classList.add('disabled');
-                               });
-                       }
-               },
-               
-               /**
-                * Adds the given poll option to the option list.
-                * 
-                * @param       {object[]}      pollOptions     data of the added options
-                */
-               _createOptionList: function(pollOptions) {
-                       for (var i = 0, length = pollOptions.length; i < length; i++) {
-                               var option = pollOptions[i];
-                               this._createOption(option.optionValue, option.optionID);
-                       }
-                       
-                       // add empty option field to add new options
-                       if (this._optionCount < this._options.maxOptions) {
-                               this._createOption();
-                       }
-               },
-               
-               /**
-                * Handles errors when the data is saved via AJAX.
-                * 
-                * @param       {object}        data    request response data
-                */
-               _handleError: function (data) {
-                       switch (data.returnValues.fieldName) {
-                               case this._wysiwygId + 'Poll_endTime':
-                               case this._wysiwygId + 'Poll_maxVotes':
-                                       var fieldName = data.returnValues.fieldName.replace(this._wysiwygId + 'Poll_', '');
-                                       
-                                       var small = elCreate('small');
-                                       small.className = 'innerError';
-                                       small.innerHTML = Language.get('wcf.poll.' + fieldName + '.error.' + data.returnValues.errorType);
-                                       
-                                       var element = elById(data.returnValues.fieldName);
-                                       var errorParent = element.closest('dd');
-                                       
-                                       DomUtil.prepend(small, element.nextSibling);
-                                       
-                                       data.cancel = true;
-                                       break;
-                       }
-               },
-               
-               /**
-                * Adds an empty poll option after the current option when clicking enter.
-                * 
-                * @param       {Event}         event   key event
-                */
-               _optionInputKeyDown: function(event) {
-                       // ignore every key except for [Enter]
-                       if (!EventKey.Enter(event)) {
-                               return;
-                       }
-                       
-                       Core.triggerEvent(elByClass('jsAddOption', event.currentTarget.parentNode)[0], 'click');
-                       
-                       event.preventDefault();
-               },
-               
-               /**
-                * Removes a poll option after clicking on the `Remove Option` button.
-                * 
-                * @param       {Event}         event   click event
-                */
-               _removeOption: function (event) {
-                       event.preventDefault();
-                       
-                       elRemove(event.currentTarget.closest('li'));
-                       
-                       this._optionCount--;
-                       
-                       elBySelAll('span.jsAddOption', this.optionList, function(icon) {
-                               icon.classList.add('pointer');
-                               icon.classList.remove('disabled');
-                       });
-                       
-                       if (this.optionList.length === 0) {
-                               this._createOption();
-                       }
-               },
-               
-               /**
-                * Resets all poll-related form fields.
-                */
-               _reset: function() {
-                       this.questionField.value = '';
-                       
-                       this._optionCount = 0;
-                       this.optionList.innerHtml = '';
-                       this._createOption();
-                       
-                       DatePicker.clear(this.endTimeField);
-                       
-                       this.maxVotesField.value = 1;
-                       this.isChangeableYesField.checked = false;
-                       this.isChangeableNoField.checked = true;
-                       this.isPublicYesField = false;
-                       this.isPublicNoField = true;
-                       this.resultsRequireVoteYesField = false;
-                       this.resultsRequireVoteNoField = true;
-                       this.sortByVotesYesField = false;
-                       this.sortByVotesNoField = true;
-                       
-                       EventHandler.fire(
-                               'com.woltlab.wcf.poll.editor',
-                               'reset',
-                               {
-                                       pollEditor: this
-                               }
-                       );
-               },
-               
-               /**
-                * Is called if the form is submitted or before the AJAX request is sent.
-                * 
-                * @param       {Event?}        event   form submit event
-                */
-               _submit: function(event) {
-                       var options = [];
-                       for (var i = 0, length = this.optionList.children.length; i < length; i++) {
-                               var listItem = this.optionList.children[i];
-                               var optionValue = elBySel('input[type=text]', listItem).value.trim();
-                               
-                               if (optionValue !== '') {
-                                       options.push(elData(listItem, 'option-id') + '_' + optionValue);
-                               }
-                       }
-                       
-                       if (this._options.isAjax) {
-                               event.poll = {};
-                               
-                               event.poll[this.questionField.id] = this.questionField.value;
-                               event.poll[this._wysiwygId + 'Poll_options'] = options;
-                               event.poll[this.endTimeField.id] = this.endTimeField.value;
-                               event.poll[this.maxVotesField.id] = this.maxVotesField.value;
-                               event.poll[this.isChangeableYesField.id] = !!this.isChangeableYesField.checked;
-                               event.poll[this.isPublicYesField.id] = !!this.isPublicYesField.checked;
-                               event.poll[this.resultsRequireVoteYesField.id] = !!this.resultsRequireVoteYesField.checked;
-                               event.poll[this.sortByVotesYesField.id] = !!this.sortByVotesYesField.checked;
-                               
-                               EventHandler.fire(
-                                       'com.woltlab.wcf.poll.editor',
-                                       'submit',
-                                       {
-                                               event: event,
-                                               pollEditor: this
-                                       }
-                               );
-                       }
-                       else {
-                               var form = this._container.closest('form');
-                               
-                               for (var i = 0, length = options.length; i < length; i++) {
-                                       var input = elCreate('input');
-                                       elAttr(input, 'type', 'hidden');
-                                       elAttr(input, 'name', this._wysiwygId + 'Poll_options[' + i + ']');
-                                       input.value = options[i];
-                                       form.appendChild(input);
-                               }
-                       }
-               },
-               
-               /**
-                * Is called to validate the poll data.
-                * 
-                * @param       {object}        data    event data
-                */
-               _validate: function(data) {
-                       if (this.questionField.value.trim() === '') {
-                               return;
-                       }
-                       
-                       var nonEmptyOptionCount = 0;
-                       for (var i = 0, length = this.optionList.children.length; i < length; i++) {
-                               var optionInput = elBySel('input[type=text]', this.optionList.children[i]);
-                               if (optionInput.value.trim() !== '') {
-                                       nonEmptyOptionCount++;
-                               }
-                       }
-                       
-                       if (nonEmptyOptionCount === 0) {
-                               data.api.throwError(this._container, Language.get('wcf.global.form.error.empty'));
-                               data.valid = false;
-                       }
-                       else {
-                               var maxVotes = ~~this.maxVotesField.value;
-                               
-                               if (maxVotes && maxVotes > nonEmptyOptionCount) {
-                                       data.api.throwError(this.maxVotesField.parentNode, Language.get('wcf.poll.maxVotes.error.invalid'));
-                                       data.valid = false;
-                               }
-                               else {
-                                       EventHandler.fire(
-                                               'com.woltlab.wcf.poll.editor',
-                                               'validate',
-                                               {
-                                                       data: data,
-                                                       pollEditor: this
-                                               }
-                                       );
-                               }
-                       }
-               }
-       };
-       
-       return UiPollEditor;
-});
-
-/**
- * Converts `<woltlab-metacode>` into the bbcode representation.
- * 
- * @author      Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module      WoltLabSuite/Core/Ui/Redactor/Article
- */
-define('WoltLabSuite/Core/Ui/Redactor/Article',['WoltLabSuite/Core/Ui/Article/Search'], function(UiArticleSearch) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _click: function() {},
-                       _insert: function() {}
-               };
-               return Fake;
-       }
-       
-       function UiRedactorArticle(editor, button) { this.init(editor, button); }
-       UiRedactorArticle.prototype = {
-               init: function (editor, button) {
-                       this._editor = editor;
-                       
-                       button.addEventListener(WCF_CLICK_EVENT, this._click.bind(this));
-               },
-               
-               _click: function (event) {
-                       event.preventDefault();
-                       
-                       UiArticleSearch.open(this._insert.bind(this));
-               },
-               
-               _insert: function (articleId) {
-                       this._editor.buffer.set();
-                       
-                       this._editor.insert.text("[wsa='" + articleId + "'][/wsa]");
-               }
-       };
-       
-       return UiRedactorArticle;
-});
-
-/**
- * Converts `<woltlab-metacode>` into the bbcode representation.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Redactor/Metacode
- */
-define('WoltLabSuite/Core/Ui/Redactor/Metacode',['EventHandler', 'Dom/Util'], function(EventHandler, DomUtil) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       convert: function() {},
-                       convertFromHtml: function() {},
-                       _getOpeningTag: function() {},
-                       _getClosingTag: function() {},
-                       _getFirstParagraph: function() {},
-                       _getLastParagraph: function() {},
-                       _parseAttributes: function() {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Redactor/Metacode
-        */
-       return {
-               /**
-                * Converts `<woltlab-metacode>` into the bbcode representation.
-                * 
-                * @param       {Element}       element         textarea element
-                */
-               convert: function(element) {
-                       element.textContent = this.convertFromHtml(element.textContent);
-               },
-               
-               convertFromHtml: function (editorId, html) {
-                       var div = elCreate('div');
-                       div.innerHTML = html;
-                       
-                       var attributes, data, metacode, metacodes = elByTag('woltlab-metacode', div), name, tagClose, tagOpen;
-                       while (metacodes.length) {
-                               metacode = metacodes[0];
-                               name = elData(metacode, 'name');
-                               attributes = this._parseAttributes(elData(metacode, 'attributes'));
-                               
-                               data = {
-                                       attributes: attributes,
-                                       cancel: false,
-                                       metacode: metacode
-                               };
-                               
-                               EventHandler.fire('com.woltlab.wcf.redactor2', 'metacode_' + name + '_' + editorId, data);
-                               if (data.cancel === true) {
-                                       continue;
-                               }
-                               
-                               tagOpen = this._getOpeningTag(name, attributes);
-                               tagClose = this._getClosingTag(name);
-                               
-                               if (metacode.parentNode === div) {
-                                       DomUtil.prepend(tagOpen, this._getFirstParagraph(metacode));
-                                       this._getLastParagraph(metacode).appendChild(tagClose);
-                               }
-                               else {
-                                       DomUtil.prepend(tagOpen, metacode);
-                                       metacode.appendChild(tagClose);
-                               }
-                               
-                               DomUtil.unwrapChildNodes(metacode);
-                       }
-                       
-                       // convert `<kbd>…</kbd>` to `[tt]…[/tt]`
-                       var inlineCode, inlineCodes = elByTag('kbd', div);
-                       while (inlineCodes.length) {
-                               inlineCode = inlineCodes[0];
-                               
-                               inlineCode.insertBefore(document.createTextNode('[tt]'), inlineCode.firstChild);
-                               inlineCode.appendChild(document.createTextNode('[/tt]'));
-                               
-                               DomUtil.unwrapChildNodes(inlineCode);
-                       }
-                       
-                       return div.innerHTML;
-               },
-               
-               /**
-                * Returns a text node representing the opening bbcode tag.
-                * 
-                * @param       {string}        name            bbcode tag
-                * @param       {Array}         attributes      list of attributes
-                * @returns     {Text}          text node containing the opening bbcode tag
-                * @protected
-                */
-               _getOpeningTag: function(name, attributes) {
-                       var buffer = '[' + name;
-                       if (attributes.length) {
-                               buffer += '=';
-                               
-                               for (var i = 0, length = attributes.length; i < length; i++) {
-                                       if (i > 0) buffer += ",";
-                                       buffer += "'" + attributes[i] + "'";
-                               }
-                       }
-                       
-                       return document.createTextNode(buffer + ']');
-               },
-               
-               /**
-                * Returns a text node representing the closing bbcode tag.
-                * 
-                * @param       {string}        name            bbcode tag
-                * @returns     {Text}          text node containing the closing bbcode tag
-                * @protected
-                */
-               _getClosingTag: function(name) {
-                       return document.createTextNode('[/' + name + ']');
-               },
-               
-               /**
-                * Returns the first paragraph of provided element. If there are no children or
-                * the first child is not a paragraph, a new paragraph is created and inserted
-                * as first child.
-                * 
-                * @param       {Element}       element         metacode element
-                * @returns     {Element}       paragraph that is the first child of provided element
-                * @protected
-                */
-               _getFirstParagraph: function (element) {
-                       var firstChild, paragraph;
-                       
-                       if (element.childElementCount === 0) {
-                               paragraph = elCreate('p');
-                               element.appendChild(paragraph);
-                       }
-                       else {
-                               firstChild = element.children[0];
-                               
-                               if (firstChild.nodeName === 'P') {
-                                       paragraph = firstChild;
-                               }
-                               else {
-                                       paragraph = elCreate('p');
-                                       element.insertBefore(paragraph, firstChild);
-                               }
-                       }
-                       
-                       return paragraph;
-               },
-               
-               /**
-                * Returns the last paragraph of provided element. If there are no children or
-                * the last child is not a paragraph, a new paragraph is created and inserted
-                * as last child.
-                * 
-                * @param       {Element}       element         metacode element
-                * @returns     {Element}       paragraph that is the last child of provided element
-                * @protected
-                */
-               _getLastParagraph: function (element) {
-                       var count = element.childElementCount, lastChild, paragraph;
-                       
-                       if (count === 0) {
-                               paragraph = elCreate('p');
-                               element.appendChild(paragraph);
-                       }
-                       else {
-                               lastChild = element.children[count - 1];
-                               
-                               if (lastChild.nodeName === 'P') {
-                                       paragraph = lastChild;
-                               }
-                               else {
-                                       paragraph = elCreate('p');
-                                       element.appendChild(paragraph);
-                               }
-                       }
-                       
-                       return paragraph;
-               },
-               
-               /**
-                * Parses the attributes string.
-                * 
-                * @param       {string}        attributes      base64- and JSON-encoded attributes
-                * @return      {Array}         list of parsed attributes
-                * @protected
-                */
-               _parseAttributes: function(attributes) {
-                       try {
-                               attributes = JSON.parse(atob(attributes));
-                       }
-                       catch (e) { /* invalid base64 data or invalid json */ }
-                       
-                       if (!Array.isArray(attributes)) {
-                               return [];
-                       }
-                       
-                       var attribute, parsedAttributes = [];
-                       for (var i = 0, length = attributes.length; i < length; i++) {
-                               attribute = attributes[i];
-                               
-                               if (typeof attribute === 'string') {
-                                       attribute = attribute.replace(/^'(.*)'$/, '$1');
-                               }
-                               
-                               parsedAttributes.push(attribute);
-                       }
-                       
-                       return parsedAttributes;
-               }
-       };
-});
-
-/**
- * Manages the autosave process storing the current editor message in the local
- * storage to recover it on browser crash or accidental navigation.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Redactor/Autosave
- */
-define('WoltLabSuite/Core/Ui/Redactor/Autosave',['Core', 'Devtools', 'EventHandler', 'Language', 'Dom/Traverse', './Metacode'], function(Core, Devtools, EventHandler, Language, DomTraverse, UiRedactorMetacode) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       getInitialValue: function() {},
-                       getMetaData: function () {},
-                       watch: function() {},
-                       destroy: function() {},
-                       clear: function() {},
-                       createOverlay: function() {},
-                       hideOverlay: function() {},
-                       _saveToStorage: function() {},
-                       _cleanup: function() {}
-               };
-               return Fake;
-       }
-       
-       // time between save requests in seconds
-       var _frequency = 15;
-       
-       /**
-        * @param       {Element}       element         textarea element
-        * @constructor
-        */
-       function UiRedactorAutosave(element) { this.init(element); }
-       UiRedactorAutosave.prototype = {
-               /**
-                * Initializes the autosave handler and removes outdated messages from storage.
-                * 
-                * @param       {Element}       element         textarea element
-                */
-               init: function (element) {
-                       this._container = null;
-                       this._metaData = {};
-                       this._editor = null;
-                       this._element = element;
-                       this._isActive = true;
-                       this._isPending = false;
-                       this._key = Core.getStoragePrefix() + elData(this._element, 'autosave');
-                       this._lastMessage = '';
-                       this._originalMessage = '';
-                       this._overlay = null;
-                       this._restored = false;
-                       this._timer = null;
-                       
-                       this._cleanup();
-                       
-                       // remove attribute to prevent Redactor's built-in autosave to kick in
-                       this._element.removeAttribute('data-autosave');
-                       
-                       var form = DomTraverse.parentByTag(this._element, 'FORM');
-                       if (form !== null) {
-                               form.addEventListener('submit', this.destroy.bind(this));
-                       }
-                       
-                       // export meta data
-                       EventHandler.add('com.woltlab.wcf.redactor2', 'getMetaData_' + this._element.id, (function (data) {
-                               for (var key in this._metaData) {
-                                       if (this._metaData.hasOwnProperty(key)) {
-                                               data[key] = this._metaData[key];
-                                       }
-                               }
-                       }).bind(this));
-                       
-                       // clear editor content on reset
-                       EventHandler.add('com.woltlab.wcf.redactor2', 'reset_' + this._element.id, this.hideOverlay.bind(this));
-                       
-                       document.addEventListener('visibilitychange', this._onVisibilityChange.bind(this));
-               },
-               
-               _onVisibilityChange: function () {
-                       if (document.hidden) {
-                               this._isActive = false;
-                               this._isPending = true;
-                       }
-                       else {
-                               this._isActive = true;
-                               this._isPending = false;
-                       }
-               },
-               
-               /**
-                * Returns the initial value for the textarea, used to inject message
-                * from storage into the editor before initialization.
-                * 
-                * @return      {string}        message content
-                */
-               getInitialValue: function() {
-                       //noinspection JSUnresolvedVariable
-                       if (window.ENABLE_DEVELOPER_TOOLS && Devtools._internal_.editorAutosave() === false) {
-                               //noinspection JSUnresolvedVariable
-                               return this._element.value;
-                       }
-                       
-                       var value = '';
-                       try {
-                               value = window.localStorage.getItem(this._key);
-                       }
-                       catch (e) {
-                               window.console.warn("Unable to access local storage: " + e.message);
-                       }
-                       
-                       try {
-                               value = JSON.parse(value);
-                       }
-                       catch (e) {
-                               value = '';
-                       }
-                       
-                       // Check if the storage is outdated.
-                       if (value !== null && typeof value === 'object' && value.content) {
-                               var lastEditTime = ~~elData(this._element, 'autosave-last-edit-time');
-                               if (lastEditTime * 1000 <= value.timestamp) {
-                                       // Compare the stored version with the editor content, but only use the `innerText` property
-                                       // in order to ignore differences in whitespace, e. g. caused by indentation of HTML tags.
-                                       var div1 = elCreate('div');
-                                       div1.innerHTML = this._element.value;
-                                       var div2 = elCreate('div');
-                                       div2.innerHTML = value.content;
-                                       
-                                       if (div1.innerText.trim() !== div2.innerText.trim()) {
-                                               //noinspection JSUnresolvedVariable
-                                               this._originalMessage = this._element.value;
-                                               this._restored = true;
-                                               
-                                               this._metaData = value.meta || {};
-                                               
-                                               return value.content;
-                                       }
-                               }
-                       }
-                       
-                       //noinspection JSUnresolvedVariable
-                       return this._element.value;
-               },
-               
-               /**
-                * Returns the stored meta data.
-                * 
-                * @return      {Object}
-                */
-               getMetaData: function () {
-                       return this._metaData;
-               },
-               
-               /**
-                * Enables periodical save of editor contents to local storage.
-                * 
-                * @param       {$.Redactor}    editor  redactor instance
-                */
-               watch: function(editor) {
-                       this._editor = editor;
-                       
-                       if (this._timer !== null) {
-                               throw new Error("Autosave timer is already active.");
-                       }
-                       
-                       this._timer = window.setInterval(this._saveToStorage.bind(this), _frequency * 1000);
-                       
-                       this._saveToStorage();
-                       
-                       this._isPending = false;
-               },
-               
-               /**
-                * Disables autosave handler, for use on editor destruction.
-                */
-               destroy: function () {
-                       this.clear();
-                       
-                       this._editor = null;
-                       
-                       window.clearInterval(this._timer);
-                       this._timer = null;
-                       this._isPending = false;
-               },
-               
-               /**
-                * Removed the stored message, for use after a message has been submitted.
-                */
-               clear: function () {
-                       this._metaData = {};
-                       this._lastMessage = '';
-                       
-                       try {
-                               window.localStorage.removeItem(this._key);
-                       }
-                       catch (e) {
-                               window.console.warn("Unable to remove from local storage: " + e.message);
-                       }
-               },
-               
-               /**
-                * Creates the autosave controls, used to keep or discard the restored draft.
-                */
-               createOverlay: function () {
-                       if (!this._restored) {
-                               return;
-                       }
-                       
-                       var container = elCreate('div');
-                       container.className = 'redactorAutosaveRestored active';
-                       
-                       var title = elCreate('span');
-                       title.textContent = Language.get('wcf.editor.autosave.restored');
-                       container.appendChild(title);
-                       
-                       var button = elCreate('a');
-                       button.className = 'jsTooltip';
-                       button.href = '#';
-                       button.title = Language.get('wcf.editor.autosave.keep');
-                       button.innerHTML = '<span class="icon icon16 fa-check green"></span>';
-                       button.addEventListener(WCF_CLICK_EVENT, (function (event) {
-                               event.preventDefault();
-                               
-                               this.hideOverlay();
-                       }).bind(this));
-                       container.appendChild(button);
-                       
-                       button = elCreate('a');
-                       button.className = 'jsTooltip';
-                       button.href = '#';
-                       button.title = Language.get('wcf.editor.autosave.discard');
-                       button.innerHTML = '<span class="icon icon16 fa-times red"></span>';
-                       button.addEventListener(WCF_CLICK_EVENT, (function (event) {
-                               event.preventDefault();
-                               
-                               // remove from storage
-                               this.clear();
-                               
-                               // set code
-                               var content = UiRedactorMetacode.convertFromHtml(this._editor.core.element()[0].id, this._originalMessage);
-                               this._editor.code.start(content);
-                               
-                               // set value
-                               this._editor.core.textarea().val(this._editor.clean.onSync(this._editor.$editor.html()));
-                               
-                               this.hideOverlay();
-                       }).bind(this));
-                       container.appendChild(button);
-                       
-                       this._editor.core.box()[0].appendChild(container);
-                       
-                       var callback = (function () {
-                               this._editor.core.editor()[0].removeEventListener(WCF_CLICK_EVENT, callback);
-                               
-                               this.hideOverlay();
-                       }).bind(this);
-                       this._editor.core.editor()[0].addEventListener(WCF_CLICK_EVENT, callback);
-                       
-                       this._container = container;
-               },
-               
-               /**
-                * Hides the autosave controls.
-                */
-               hideOverlay: function () {
-                       if (this._container !== null) {
-                               this._container.classList.remove('active');
-                               
-                               window.setTimeout((function () {
-                                       if (this._container !== null) {
-                                               elRemove(this._container);
-                                       }
-                                       
-                                       this._container = null;
-                                       this._originalMessage = '';
-                               }).bind(this), 1000);
-                       }
-               },
-               
-               /**
-                * Saves the current message to storage unless there was no change.
-                * 
-                * @protected
-                */
-               _saveToStorage: function() {
-                       if (!this._isActive) {
-                               if (!this._isPending) return;
-                               
-                               // save one last time before suspending
-                               this._isPending = false;
-                       }
-                       
-                       //noinspection JSUnresolvedVariable
-                       if (window.ENABLE_DEVELOPER_TOOLS && Devtools._internal_.editorAutosave() === false) {
-                               //noinspection JSUnresolvedVariable
-                               return;
-                       }
-                       
-                       var content = this._editor.code.get();
-                       if (this._editor.utils.isEmpty(content)) {
-                               content = '';
-                       }
-                       
-                       if (this._lastMessage === content) {
-                               // break if content hasn't changed
-                               return;
-                       }
-                       
-                       if (content === '') {
-                               return this.clear();
-                       }
-                       
-                       try {
-                               EventHandler.fire('com.woltlab.wcf.redactor2', 'autosaveMetaData_' + this._element.id, this._metaData);
-                               
-                               window.localStorage.setItem(this._key, JSON.stringify({
-                                       content: content,
-                                       meta: this._metaData,
-                                       timestamp: Date.now()
-                               }));
-                               
-                               this._lastMessage = content;
-                       }
-                       catch (e) {
-                               window.console.warn("Unable to write to local storage: " + e.message);
-                       }
-               },
-               
-               /**
-                * Removes stored messages older than one week.
-                * 
-                * @protected
-                */
-               _cleanup: function () {
-                       var oneWeekAgo = Date.now() - (7 * 24 * 3600 * 1000), removeKeys = [];
-                       var i, key, length, value;
-                       for (i = 0, length = window.localStorage.length; i < length; i++) {
-                               key = window.localStorage.key(i);
-                               
-                               // check if key matches our prefix
-                               if (key.indexOf(Core.getStoragePrefix()) !== 0) {
-                                       continue;
-                               }
-                               
-                               try {
-                                       value = window.localStorage.getItem(key);
-                               }
-                               catch (e) {
-                                       window.console.warn("Unable to access local storage: " + e.message);
-                               }
-                               
-                               try {
-                                       value = JSON.parse(value);
-                               }
-                               catch (e) {
-                                       value = { timestamp: 0 };
-                               }
-                               
-                               if (!value || value.timestamp < oneWeekAgo) {
-                                       removeKeys.push(key);
-                               }
-                       }
-                       
-                       for (i = 0, length = removeKeys.length; i < length; i++) {
-                               try {
-                                       window.localStorage.removeItem(removeKeys[i]);
-                               }
-                               catch (e) {
-                                       window.console.warn("Unable to remove from local storage: " + e.message);
-                               }
-                       }
-               }
-       };
-       
-       return UiRedactorAutosave;
-});
-
-/**
- * Helper class to deal with clickable block headers using the pseudo
- * `::before` element.
- * 
- * @author      Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module      WoltLabSuite/Core/Ui/Redactor/PseudoHeader
- */
-define('WoltLabSuite/Core/Ui/Redactor/PseudoHeader',[], function() {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       getHeight: function() {}
-               };
-               return Fake;
-       }
-       
-       return {
-               /**
-                * Returns the height within a click should be treated as a click
-                * within the block element's title. This method expects that the
-                * `::before` element is used and that removing the attribute
-                * `data-title` does cause the title to collapse.
-                * 
-                * @param       {Element}       element         block element
-                * @return      {int}           clickable height spanning from the top border down to the bottom of the title
-                */
-               getHeight: function (element) {
-                       var height = ~~window.getComputedStyle(element).paddingTop.replace(/px$/, '');
-                       
-                       var styles = window.getComputedStyle(element, '::before');
-                       height += ~~styles.paddingTop.replace(/px$/, '');
-                       height += ~~styles.paddingBottom.replace(/px$/, '');
-                       
-                       var titleHeight = ~~styles.height.replace(/px$/, '');
-                       if (titleHeight === 0) {
-                               // firefox returns garbage for pseudo element height
-                               // https://bugzilla.mozilla.org/show_bug.cgi?id=925694
-                               
-                               titleHeight = element.scrollHeight;
-                               element.classList.add('redactorCalcHeight');
-                               titleHeight -= element.scrollHeight;
-                               element.classList.remove('redactorCalcHeight');
-                       }
-                       
-                       height += titleHeight;
-                       
-                       return height;
-               }
-       }
-});
-
-/**
- * Manages code blocks.
- *
- * @author      Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module      WoltLabSuite/Core/Ui/Redactor/Code
- */
-define('WoltLabSuite/Core/Ui/Redactor/Code',['EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog', './PseudoHeader', 'prism/prism-meta'], function (EventHandler, EventKey, Language, StringUtil, DomUtil, UiDialog, UiRedactorPseudoHeader, PrismMeta) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _bbcodeCode: function() {},
-                       _observeLoad: function() {},
-                       _edit: function() {},
-                       _setTitle: function() {},
-                       _delete: function() {},
-                       _dialogSetup: function() {},
-                       _dialogSubmit: function() {}
-               };
-               return Fake;
-       }
-       
-       var _headerHeight = 0;
-       
-       /**
-        * @param       {Object}        editor  editor instance
-        * @constructor
-        */
-       function UiRedactorCode(editor) { this.init(editor); }
-       UiRedactorCode.prototype = {
-               /**
-                * Initializes the source code management.
-                * 
-                * @param       {Object}        editor  editor instance
-                */
-               init: function(editor) {
-                       this._editor = editor;
-                       this._elementId = this._editor.$element[0].id;
-                       this._pre = null;
-                       
-                       EventHandler.add('com.woltlab.wcf.redactor2', 'bbcode_code_' + this._elementId, this._bbcodeCode.bind(this));
-                       EventHandler.add('com.woltlab.wcf.redactor2', 'observe_load_' + this._elementId, this._observeLoad.bind(this));
-                       
-                       // support for active button marking
-                       this._editor.opts.activeButtonsStates.pre = 'code';
-                       
-                       // static bind to ensure that removing works
-                       this._callbackEdit = this._edit.bind(this);
-                       
-                       // bind listeners on init
-                       this._observeLoad();
-               },
-               
-               /**
-                * Intercepts the insertion of `[code]` tags and uses a native `<pre>` instead.
-                * 
-                * @param       {Object}        data    event data
-                * @protected
-                */
-               _bbcodeCode: function(data) {
-                       data.cancel = true;
-                       
-                       var pre = this._editor.selection.block();
-                       if (pre && pre.nodeName === 'PRE' && pre.classList.contains('woltlabHtml')) {
-                               return;
-                       }
-                       
-                       this._editor.button.toggle({}, 'pre', 'func', 'block.format');
-                       
-                       pre = this._editor.selection.block();
-                       if (pre && pre.nodeName === 'PRE' && !pre.classList.contains('woltlabHtml')) {
-                               if (pre.childElementCount === 1 && pre.children[0].nodeName === 'BR') {
-                                       // drop superfluous linebreak
-                                       pre.removeChild(pre.children[0]);
-                               }
-                               
-                               this._setTitle(pre);
-                               
-                               pre.addEventListener(WCF_CLICK_EVENT, this._callbackEdit);
-                               
-                               // work-around for Safari
-                               this._editor.caret.end(pre);
-                       }
-               },
-               
-               /**
-                * Binds event listeners and sets quote title on both editor
-                * initialization and when switching back from code view.
-                * 
-                * @protected
-                */
-               _observeLoad: function() {
-                       elBySelAll('pre:not(.woltlabHtml)', this._editor.$editor[0], (function(pre) {
-                               pre.addEventListener('mousedown', this._callbackEdit);
-                               this._setTitle(pre);
-                       }).bind(this));
-               },
-               
-               /**
-                * Opens the dialog overlay to edit the code's properties.
-                * 
-                * @param       {Event}         event           event object
-                * @protected
-                */
-               _edit: function(event) {
-                       var pre = event.currentTarget;
-                       
-                       if (_headerHeight === 0) {
-                               _headerHeight = UiRedactorPseudoHeader.getHeight(pre);
-                       }
-                       
-                       // check if the click hit the header
-                       var offset = DomUtil.offset(pre);
-                       if (event.pageY > offset.top && event.pageY < (offset.top + _headerHeight)) {
-                               event.preventDefault();
-                               
-                               this._editor.selection.save();
-                               this._pre = pre;
-                               
-                               UiDialog.open(this);
-                       }
-               },
-               
-               /**
-                * Saves the changes to the code's properties.
-                * 
-                * @protected
-                */
-               _dialogSubmit: function() {
-                       var id = 'redactor-code-' + this._elementId;
-                       
-                       ['file', 'highlighter', 'line'].forEach((function (attr) {
-                               elData(this._pre, attr, elById(id + '-' + attr).value);
-                       }).bind(this));
-                       
-                       this._setTitle(this._pre);
-                       this._editor.caret.after(this._pre);
-                       
-                       UiDialog.close(this);
-               },
-               
-               /**
-                * Sets or updates the code's header title.
-                * 
-                * @param       {Element}       pre     code element
-                * @protected
-                */
-               _setTitle: function(pre) {
-                       var file = elData(pre, 'file'),
-                           highlighter = elData(pre, 'highlighter');
-                       
-                       //noinspection JSUnresolvedVariable
-                       highlighter = (this._editor.opts.woltlab.highlighters.hasOwnProperty(highlighter)) ? this._editor.opts.woltlab.highlighters[highlighter] : '';
-                       
-                       var title = Language.get('wcf.editor.code.title', {
-                               file: file,
-                               highlighter: highlighter
-                       });
-                       
-                       if (elData(pre, 'title') !== title) {
-                               elData(pre, 'title', title);
-                       }
-               },
-               
-               _delete: function (event) {
-                       event.preventDefault();
-                       
-                       var caretEnd = this._pre.nextElementSibling || this._pre.previousElementSibling;
-                       if (caretEnd === null && this._pre.parentNode !== this._editor.core.editor()[0]) {
-                               caretEnd = this._pre.parentNode;
-                       }
-                       
-                       if (caretEnd === null) {
-                               this._editor.code.set('');
-                               this._editor.focus.end();
-                       }
-                       else {
-                               elRemove(this._pre);
-                               this._editor.caret.end(caretEnd);
-                       }
-                       
-                       UiDialog.close(this);
-               },
-               
-               _dialogSetup: function() {
-                       var id = 'redactor-code-' + this._elementId,
-                           idButtonDelete = id + '-button-delete',
-                           idButtonSave = id + '-button-save',
-                           idFile = id + '-file',
-                           idHighlighter = id + '-highlighter',
-                           idLine = id + '-line';
-                       
-                       return {
-                               id: id,
-                               options: {
-                                       onClose: (function () {
-                                               this._editor.selection.restore();
-                                               
-                                               UiDialog.destroy(this);
-                                       }).bind(this),
-                                       
-                                       onSetup: (function() {
-                                               elById(idButtonDelete).addEventListener(WCF_CLICK_EVENT, this._delete.bind(this));
-                                               
-                                               // set highlighters
-                                               var highlighters = '<option value="">' + Language.get('wcf.editor.code.highlighter.detect') + '</option>';
-                                               highlighters += '<option value="plain">' + Language.get('wcf.editor.code.highlighter.plain') + '</option>';
-                                               
-                                               //noinspection JSUnresolvedVariable
-                                               var values = this._editor.opts.woltlab.highlighters.map(function (highlighter) {
-                                                       return [highlighter, PrismMeta[highlighter].title];
-                                               });
-                                               
-                                               // sort by label
-                                               values.sort(function(a, b) {
-                                                       if (a[1] < b[1]) {
-                                                               return  -1;
-                                                       }
-                                                       else if (a[1] > b[1]) {
-                                                               return 1;
-                                                       }
-                                                       
-                                                       return 0;
-                                               });
-                                               
-                                               values.forEach((function(value) {
-                                                       highlighters += '<option value="' + value[0] + '">' + StringUtil.escapeHTML(value[1]) + '</option>';
-                                               }).bind(this));
-                                               
-                                               elById(idHighlighter).innerHTML = highlighters;
-                                       }).bind(this),
-                                       
-                                       onShow: (function() {
-                                               elById(idHighlighter).value = elData(this._pre, 'highlighter');
-                                               var line = elData(this._pre, 'line');
-                                               elById(idLine).value = (line === '') ? 1 : ~~line;
-                                               elById(idFile).value = elData(this._pre, 'file');
-                                       }).bind(this),
-                                       
-                                       title: Language.get('wcf.editor.code.edit')
-                               },
-                               source: '<div class="section">'
-                                       + '<dl>'
-                                               + '<dt><label for="' + idHighlighter + '">' + Language.get('wcf.editor.code.highlighter') + '</label></dt>'
-                                               + '<dd>'
-                                                       + '<select id="' + idHighlighter + '"></select>'
-                                                       + '<small>' + Language.get('wcf.editor.code.highlighter.description') + '</small>'
-                                               + '</dd>'
-                                       + '</dl>'
-                                       + '<dl>'
-                                               + '<dt><label for="' + idLine + '">' + Language.get('wcf.editor.code.line') + '</label></dt>'
-                                               + '<dd>'
-                                                       + '<input type="number" id="' + idLine + '" min="0" value="1" class="long" data-dialog-submit-on-enter="true">'
-                                                       + '<small>' + Language.get('wcf.editor.code.line.description') + '</small>'
-                                               + '</dd>'
-                                       + '</dl>'
-                                       + '<dl>'
-                                               + '<dt><label for="' + idFile + '">' + Language.get('wcf.editor.code.file') + '</label></dt>'
-                                               + '<dd>'
-                                                       + '<input type="text" id="' + idFile + '" class="long" data-dialog-submit-on-enter="true">'
-                                                       + '<small>' + Language.get('wcf.editor.code.file.description') + '</small>'
-                                               + '</dd>'
-                                       + '</dl>'
-                               + '</div>'
-                               + '<div class="formSubmit">'
-                                       + '<button id="' + idButtonSave + '" class="buttonPrimary" data-type="submit">' + Language.get('wcf.global.button.save') + '</button>'
-                                       + '<button id="' + idButtonDelete + '">' + Language.get('wcf.global.button.delete') + '</button>'
-                               + '</div>'
-                       };
-               }
-       };
-       
-       return UiRedactorCode;
-});
-
-/**
- * Provides helper methods to add and remove format elements. These methods should in
- * theory work with non-editor elements but has not been tested and any usage outside
- * the editor is not recommended.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Redactor/Format
- */
-define('WoltLabSuite/Core/Ui/Redactor/Format',['Dom/Util'], function(DomUtil) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       format: function() {},
-                       removeFormat: function() {},
-                       _handleParentNodes: function() {},
-                       _getLastMatchingParent: function() {},
-                       _isBoundaryElement: function() {},
-                       _getSelectionMarker: function() {}
-               };
-               return Fake;
-       }
-       
-       var _isValidSelection = function(editorElement) {
-               var element = window.getSelection().anchorNode;
-               while (element) {
-                       if (element === editorElement) {
-                               return true;
-                       }
-                       
-                       element = element.parentNode;
-               }
-               
-               return false;
-       };
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Redactor/Format
-        */
-       return {
-               /**
-                * Applies format elements to the selected text.
-                * 
-                * @param       {Element}       editorElement   editor element
-                * @param       {string}        property        CSS property name
-                * @param       {string}        value           CSS property value
-                */
-               format: function(editorElement, property, value) {
-                       var selection = window.getSelection();
-                       if (!selection.rangeCount) {
-                               // no active selection
-                               return;
-                       }
-                       
-                       if (!_isValidSelection(editorElement)) {
-                               console.error("Invalid selection, range exists outside of the editor:", selection.anchorNode);
-                               return;
-                       }
-                       
-                       var range = selection.getRangeAt(0);
-                       var markerStart = null, markerEnd = null, tmpElement = null;
-                       if (range.collapsed) {
-                               tmpElement = elCreate('strike');
-                               tmpElement.textContent = '\u200B';
-                               range.insertNode(tmpElement);
-                               
-                               range = document.createRange();
-                               range.selectNodeContents(tmpElement);
-                               
-                               selection.removeAllRanges();
-                               selection.addRange(range);
-                       }
-                       else {
-                               // removing existing format causes the selection to vanish,
-                               // these markers are used to restore it afterwards
-                               markerStart = elCreate('mark');
-                               markerEnd = elCreate('mark');
-                               
-                               var tmpRange = range.cloneRange();
-                               tmpRange.collapse(true);
-                               tmpRange.insertNode(markerStart);
-                               
-                               tmpRange = range.cloneRange();
-                               tmpRange.collapse(false);
-                               tmpRange.insertNode(markerEnd);
-                               
-                               range = document.createRange();
-                               range.setStartAfter(markerStart);
-                               range.setEndBefore(markerEnd);
-                               
-                               selection.removeAllRanges();
-                               selection.addRange(range);
-                               
-                               // remove existing format before applying new one
-                               this.removeFormat(editorElement, property);
-                               
-                               range = document.createRange();
-                               range.setStartAfter(markerStart);
-                               range.setEndBefore(markerEnd);
-                               
-                               selection.removeAllRanges();
-                               selection.addRange(range);
-                       }
-                       
-                       var selectionMarker = ['strike', 'strikethrough'];
-                       if (tmpElement === null) {
-                               selectionMarker = this._getSelectionMarker(editorElement, selection);
-                               
-                               document.execCommand(selectionMarker[1]);
-                       }
-                       
-                       var elements = elBySelAll(selectionMarker[0], editorElement), formatElement, selectElements = [], strike;
-                       for (var i = 0, length = elements.length; i < length; i++) {
-                               strike = elements[i];
-                               
-                               formatElement = elCreate('span');
-                               // we're bypassing `style.setPropertyValue()` on purpose here,
-                               // as it prevents browsers from mangling the value
-                               elAttr(formatElement, 'style', property + ': ' + value);
-                               
-                               DomUtil.replaceElement(strike, formatElement);
-                               selectElements.push(formatElement);
-                       }
-                       
-                       var count = selectElements.length;
-                       if (count) {
-                               var firstSelectedElement = selectElements[0];
-                               var lastSelectedElement = selectElements[count - 1];
-                               
-                               // check if parent is of the same format
-                               // and contains only the selected nodes
-                               if (tmpElement === null && (firstSelectedElement.parentNode === lastSelectedElement.parentNode)) {
-                                       var parent = firstSelectedElement.parentNode;
-                                       if (parent.nodeName === 'SPAN' && parent.style.getPropertyValue(property) !== '') {
-                                               if (this._isBoundaryElement(firstSelectedElement, parent, 'previous') && this._isBoundaryElement(lastSelectedElement, parent, 'next')) {
-                                                       DomUtil.unwrapChildNodes(parent);
-                                               }
-                                       }
-                               }
-                               
-                               range = document.createRange();
-                               range.setStart(firstSelectedElement, 0);
-                               range.setEnd(lastSelectedElement, lastSelectedElement.childNodes.length);
-                               
-                               selection.removeAllRanges();
-                               selection.addRange(range);
-                       }
-                       
-                       if (markerStart !== null) {
-                               elRemove(markerStart);
-                               elRemove(markerEnd);
-                       }
-               },
-               
-               /**
-                * Removes a format element from the current selection.
-                * 
-                * The removal uses a few techniques to remove the target element(s) without harming
-                * nesting nor any other formatting present. The steps taken are described below:
-                * 
-                * 1. The browser will wrap all parts of the selection into <strike> tags
-                * 
-                *      This isn't the most efficient way to isolate each selected node, but is the
-                *      most reliable way to accomplish this because the browser will insert them
-                *      exactly where the range spans without harming the node nesting.
-                *      
-                *      Basically it is a trade-off between efficiency and reliability, the performance
-                *      is still excellent but could be better at the expense of an increased complexity,
-                *      which simply doesn't exactly pay off.
-                * 
-                * 2. Iterate over each inserted <strike> and isolate all relevant ancestors
-                * 
-                *      Format tags can appear both as a child of the <strike> as well as once or multiple
-                *      times as an ancestor.
-                *      
-                *      It uses ranges to select the contents before the <strike> element up to the start
-                *      of the last matching ancestor and cuts out the nodes. The browser will ensure that
-                *      the resulting fragment will include all relevant ancestors that were present before.
-                *      
-                *      The example below will use the fictional <bar> elements as the tag to remove, the
-                *      pipe ("|") is used to denote the outer node boundaries.
-                *      
-                *      Before:
-                *      |<bar>This is <foo>a <strike>simple <bar>example</bar></strike></foo></bar>|
-                *      After:
-                *      |<bar>This is <foo>a </foo></bar>|<bar><foo>simple <bar>example</bar></strike></foo></bar>|
-                *      
-                *      As a result we can now remove <bar> both inside the <strike> element as well as
-                *      the outer <bar> without harming the effect of <bar> for the preceding siblings.
-                *      
-                *      This process is repeated for siblings appearing after the <strike> element too, it
-                *      works as described above but flipped. This is an expensive operation and will only
-                *      take place if there are any matching ancestors that need to be considered.
-                *      
-                *      Inspired by http://stackoverflow.com/a/12899461
-                * 
-                * 3. Remove all matching ancestors, child elements and last the <strike> element itself
-                * 
-                *      Depending on the amount of nested matching nodes, this process will move a lot of
-                *      nodes around. Removing the <bar> element will require all its child nodes to be moved
-                *      in front of <bar>, they will actually become a sibling of <bar>. Afterwards the
-                *      (now empty) <bar> element can be safely removed without losing any nodes.
-                * 
-                * 
-                * One last hint: This method will not check if the selection at some point contains at
-                * least one target element, it assumes that the user will not take any action that invokes
-                * this method for no reason (unless they want to waste CPU cycles, in that case they're
-                * welcome).
-                * 
-                * This is especially important for developers as this method shouldn't be called for
-                * no good reason. Even though it is super fast, it still comes with expensive DOM operations
-                * and especially low-end devices (such as cheap smartphones) might not exactly like executing
-                * this method on large documents.
-                * 
-                * If you fell the need to invoke this method anyway, go ahead. I'm a comment, not a cop.
-                * 
-                * @param       {Element}       editorElement   editor element
-                * @param       {string}        property        CSS property that should be removed
-                */
-               removeFormat: function(editorElement, property) {
-                       var selection = window.getSelection();
-                       if (!selection.rangeCount) {
-                               return;
-                       }
-                       else if (!_isValidSelection(editorElement)) {
-                               console.error("Invalid selection, range exists outside of the editor:", selection.anchorNode);
-                               return;
-                       }
-                       
-                       // Removing a span from an empty selection in an empty line containing a `<br>` causes a selection
-                       // shift where the caret is moved into the span again. Unlike inline changes to the formatting, any
-                       // removal of the format in an empty line should remove it from its entirely, instead of just around
-                       // the caret position.
-                       var range = selection.getRangeAt(0);
-                       var helperTextNode = null;
-                       if (range.collapsed) {
-                               var container = range.startContainer;
-                               var tree = [container];
-                               while (true) {
-                                       var parent = container.parentNode;
-                                       if (parent === editorElement || parent.nodeName === 'TD') {
-                                               break;
-                                       }
-                                       
-                                       container = parent;
-                                       tree.push(container);
-                               }
-                               
-                               if (this._isEmpty(container.innerHTML)) {
-                                       var marker = document.createElement('woltlab-format-marker');
-                                       range.insertNode(marker);
-                                       
-                                       // Find the offending span and remove it entirely.
-                                       tree.forEach(function (element) {
-                                               if (element.nodeName === 'SPAN') {
-                                                       if (element.style.getPropertyValue(property)) {
-                                                               DomUtil.unwrapChildNodes(element);
-                                                       }
-                                               }
-                                       });
-                                       
-                                       // Firefox messes up the selection if the ancestor element was removed and there is
-                                       // an adjacent `<br>` present. Instead of keeping the caret in front of the <br>, it
-                                       // is implicitly moved behind it.
-                                       range = document.createRange();
-                                       range.selectNode(marker);
-                                       range.collapse(true);
-                                       
-                                       selection.removeAllRanges();
-                                       selection.addRange(range);
-                                       
-                                       elRemove(marker);
-                                       
-                                       return;
-                               }
-                               
-                               // Fill up the range with a zero length whitespace to give the browser
-                               // something to strike through. If the range is completely empty, the
-                               // "strike" is remembered by the browser, but not actually inserted into
-                               // the DOM, causing the next keystroke to magically insert it.
-                               helperTextNode = document.createTextNode('\u200B');
-                               range.insertNode(helperTextNode);
-                       }
-                       
-                       var strikeElements = elByTag('strike', editorElement);
-                       
-                       // remove any <strike> element first, all though there shouldn't be any at all
-                       while (strikeElements.length) {
-                               DomUtil.unwrapChildNodes(strikeElements[0]);
-                       }
-                       
-                       var selectionMarker = this._getSelectionMarker(editorElement, window.getSelection());
-                       
-                       document.execCommand(selectionMarker[1]);
-                       if (selectionMarker[0] !== 'strike') {
-                               strikeElements = elByTag(selectionMarker[0], editorElement);
-                       }
-                       
-                       var lastMatchingParent, strikeElement;
-                       while (strikeElements.length) {
-                               strikeElement = strikeElements[0];
-                               lastMatchingParent = this._getLastMatchingParent(strikeElement, editorElement, property);
-                               
-                               if (lastMatchingParent !== null) {
-                                       this._handleParentNodes(strikeElement, lastMatchingParent, property);
-                               }
-                               
-                               // remove offending elements from child nodes
-                               elBySelAll('span', strikeElement, function (span) {
-                                       if (span.style.getPropertyValue(property)) {
-                                               DomUtil.unwrapChildNodes(span);
-                                       }
-                               });
-                               
-                               // remove strike element itself
-                               DomUtil.unwrapChildNodes(strikeElement);
-                       }
-                       
-                       // search for tags that are still floating around, but are completely empty
-                       elBySelAll('span', editorElement, function (element) {
-                               if (element.parentNode && !element.textContent.length && element.style.getPropertyValue(property) !== '') {
-                                       if (element.childElementCount === 1 && element.children[0].nodeName === 'MARK') {
-                                               element.parentNode.insertBefore(element.children[0], element);
-                                       }
-                                       
-                                       if (element.childElementCount === 0) {
-                                               elRemove(element);
-                                       }
-                               }
-                       });
-                       
-                       if (helperTextNode !== null) {
-                               window.jQuery(editorElement).redactor('caret.after', range.parentNode);
-                               elRemove(helperTextNode);
-                       }
-               },
-               
-               /**
-                * Slices relevant parent nodes and removes matching ancestors.
-                * 
-                * @param       {Element}       strikeElement           strike element representing the text selection
-                * @param       {Element}       lastMatchingParent      last matching ancestor element
-                * @param       {string}        property                CSS property that should be removed
-                * @protected
-                */
-               _handleParentNodes: function(strikeElement, lastMatchingParent, property) {
-                       var range;
-                       
-                       // selection does not begin at parent node start, slice all relevant parent
-                       // nodes to ensure that selection is then at the beginning while preserving
-                       // all proper ancestor elements
-                       // 
-                       // before: (the pipe represents the node boundary)
-                       // |otherContent <-- selection -->
-                       // after:
-                       // |otherContent| |<-- selection -->
-                       if (!DomUtil.isAtNodeStart(strikeElement, lastMatchingParent)) {
-                               range = document.createRange();
-                               range.setStartBefore(lastMatchingParent);
-                               range.setEndBefore(strikeElement);
-                               
-                               var fragment = range.extractContents();
-                               lastMatchingParent.parentNode.insertBefore(fragment, lastMatchingParent);
-                       }
-                       
-                       // selection does not end at parent node end, slice all relevant parent nodes
-                       // to ensure that selection is then at the end while preserving all proper
-                       // ancestor elements
-                       // 
-                       // before: (the pipe represents the node boundary)
-                       // <-- selection --> otherContent|
-                       // after:
-                       // <-- selection -->| |otherContent|
-                       if (!DomUtil.isAtNodeEnd(strikeElement, lastMatchingParent)) {
-                               range = document.createRange();
-                               range.setStartAfter(strikeElement);
-                               range.setEndAfter(lastMatchingParent);
-                               
-                               fragment = range.extractContents();
-                               lastMatchingParent.parentNode.insertBefore(fragment, lastMatchingParent.nextSibling);
-                       }
-                       
-                       // the strike element is now some kind of isolated, meaning we can now safely
-                       // remove all offending parent nodes without influencing formatting of any content
-                       // before or after the element
-                       elBySelAll('span', lastMatchingParent, function (span) {
-                               if (span.style.getPropertyValue(property)) {
-                                       DomUtil.unwrapChildNodes(span);
-                               }
-                       });
-                       
-                       // finally remove the parent itself
-                       DomUtil.unwrapChildNodes(lastMatchingParent);
-               },
-               
-               /**
-                * Finds the last matching ancestor until it reaches the editor element.
-                * 
-                * @param       {Element}               strikeElement   strike element representing the text selection
-                * @param       {Element}               editorElement   editor element
-                * @param       {string}                property        CSS property that should be removed
-                * @returns     {(Element|null)}        last matching ancestor element or null if there is none
-                * @protected
-                */
-               _getLastMatchingParent: function(strikeElement, editorElement, property) {
-                       var parent = strikeElement.parentNode, match = null;
-                       while (parent !== editorElement) {
-                               if (parent.nodeName === 'SPAN' && parent.style.getPropertyValue(property) !== '') {
-                                       match = parent;
-                               }
-                               
-                               parent = parent.parentNode;
-                       }
-                       
-                       return match;
-               },
-               
-               /**
-                * Returns true if provided element is the first or last element
-                * of its parent, ignoring empty text nodes appearing between the
-                * element and the boundary.
-                * 
-                * @param       {Element}       element         target element
-                * @param       {Element}       parent          parent element
-                * @param       {string}        type            traversal direction, can be either `next` or `previous`
-                * @return      {boolean}       true if element is the non-empty boundary element
-                * @protected
-                */
-               _isBoundaryElement: function (element, parent, type) {
-                       var node = element;
-                       while (node = node[type + 'Sibling']) {
-                               if (node.nodeType !== Node.TEXT_NODE || node.textContent.replace(/\u200B/, '') !== '') {
-                                       return false;
-                               }
-                       }
-                       
-                       return true;
-               },
-               
-               /**
-                * Returns a custom selection marker element, can be either `strike`, `sub` or `sup`. Using other kind
-                * of formattings is not possible due to the inconsistent behavior across browsers.
-                * 
-                * @param       {Element}       editorElement   editor element
-                * @param       {Selection}     selection       selection object
-                * @return      {string[]}      tag name and command name
-                * @protected
-                */
-               _getSelectionMarker: function (editorElement, selection) {
-                       var hasNode, node, tag, tags = ['DEL', 'SUB', 'SUP'];
-                       for (var i = 0, length = tags.length; i < length; i++) {
-                               tag = tags[i];
-                               
-                               node = elClosest(selection.anchorNode);
-                               hasNode = (elBySel(tag.toLowerCase(), node) !== null);
-                               
-                               if (!hasNode) {
-                                       while (node && node !== editorElement) {
-                                               if (node.nodeName === tag) {
-                                                       hasNode = true;
-                                                       break;
-                                               }
-                                               
-                                               node = node.parentNode;
-                                       }
-                               }
-                               
-                               if (hasNode) {
-                                       tag = undefined;
-                               }
-                               else {
-                                       break;
-                               }
-                       }
-                       
-                       if (tag === 'DEL' || tag === undefined) {
-                               return ['strike', 'strikethrough'];
-                       }
-                       
-                       return [tag.toLowerCase(), tag.toLowerCase() + 'script'];
-               },
-               
-               /**
-                * Slightly modified version of Redactor's `utils.isEmpty()`.
-                * 
-                * @param {string} html
-                * @returns {boolean}
-                * @protected
-                */
-               _isEmpty: function(html) {
-                       html = html.replace(/[\u200B-\u200D\uFEFF]/g, '');
-                       html = html.replace(/&nbsp;/gi, '');
-                       html = html.replace(/<\/?br\s?\/?>/g, '');
-                       html = html.replace(/\s/g, '');
-                       html = html.replace(/^<p>[^\W\w\D\d]*?<\/p>$/i, '');
-                       html = html.replace(/<iframe(.*?[^>])>$/i, 'iframe');
-                       html = html.replace(/<source(.*?[^>])>$/i, 'source');
-                       
-                       // remove empty tags
-                       html = html.replace(/<[^\/>][^>]*><\/[^>]+>/gi, '');
-                       html = html.replace(/<[^\/>][^>]*><\/[^>]+>/gi, '');
-                       
-                       return html.trim() === '';
-               }
-       };
-});
-
-/**
- * Manages html code blocks.
- * 
- * @author      Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module      WoltLabSuite/Core/Ui/Redactor/Html
- */
-define('WoltLabSuite/Core/Ui/Redactor/Html',['EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog', './PseudoHeader'], function (EventHandler, EventKey, Language, StringUtil, DomUtil, UiDialog, UiRedactorPseudoHeader) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _bbcodeCode: function() {},
-                       _observeLoad: function() {},
-                       _edit: function() {},
-                       _save: function() {},
-                       _setTitle: function() {},
-                       _delete: function() {},
-                       _dialogSetup: function() {}
-               };
-               return Fake;
-       }
-       
-       var _headerHeight = 0;
-       
-       /**
-        * @param       {Object}        editor  editor instance
-        * @constructor
-        */
-       function UiRedactorHtml(editor) { this.init(editor); }
-       UiRedactorHtml.prototype = {
-               /**
-                * Initializes the source code management.
-                *
-                * @param       {Object}        editor  editor instance
-                */
-               init: function(editor) {
-                       this._editor = editor;
-                       this._elementId = this._editor.$element[0].id;
-                       this._pre = null;
-                       
-                       EventHandler.add('com.woltlab.wcf.redactor2', 'bbcode_woltlabHtml_' + this._elementId, this._bbcodeCode.bind(this));
-                       EventHandler.add('com.woltlab.wcf.redactor2', 'observe_load_' + this._elementId, this._observeLoad.bind(this));
-                       
-                       // support for active button marking
-                       this._editor.opts.activeButtonsStates['woltlab-html'] = 'woltlabHtml';
-                       
-                       // static bind to ensure that removing works
-                       this._callbackEdit = this._edit.bind(this);
-                       
-                       // bind listeners on init
-                       this._observeLoad();
-               },
-               
-               /**
-                * Intercepts the insertion of `[woltlabHtml]` tags and uses a native `<pre>` instead.
-                *
-                * @param       {Object}        data    event data
-                * @protected
-                */
-               _bbcodeCode: function(data) {
-                       data.cancel = true;
-                       
-                       var pre = this._editor.selection.block();
-                       if (pre && pre.nodeName === 'PRE' && !pre.classList.contains('woltlabHtml')) {
-                               return;
-                       }
-                       
-                       this._editor.button.toggle({}, 'pre', 'func', 'block.format');
-                       
-                       pre = this._editor.selection.block();
-                       if (pre && pre.nodeName === 'PRE') {
-                               pre.classList.add('woltlabHtml');
-                               
-                               if (pre.childElementCount === 1 && pre.children[0].nodeName === 'BR') {
-                                       // drop superfluous linebreak
-                                       pre.removeChild(pre.children[0]);
-                               }
-                               
-                               this._setTitle(pre);
-                               
-                               pre.addEventListener(WCF_CLICK_EVENT, this._callbackEdit);
-                               
-                               // work-around for Safari
-                               this._editor.caret.end(pre);
-                       }
-               },
-               
-               /**
-                * Binds event listeners and sets quote title on both editor
-                * initialization and when switching back from code view.
-                *
-                * @protected
-                */
-               _observeLoad: function() {
-                       elBySelAll('pre.woltlabHtml', this._editor.$editor[0], (function(pre) {
-                               pre.addEventListener('mousedown', this._callbackEdit);
-                               this._setTitle(pre);
-                       }).bind(this));
-               },
-               
-               /**
-                * Opens the dialog overlay to edit the code's properties.
-                *
-                * @param       {Event}         event           event object
-                * @protected
-                */
-               _edit: function(event) {
-                       var pre = event.currentTarget;
-                       
-                       if (_headerHeight === 0) {
-                               _headerHeight = UiRedactorPseudoHeader.getHeight(pre);
-                       }
-                       
-                       // check if the click hit the header
-                       var offset = DomUtil.offset(pre);
-                       if (event.pageY > offset.top && event.pageY < (offset.top + _headerHeight)) {
-                               event.preventDefault();
-                               
-                               this._editor.selection.save();
-                               this._pre = pre;
-                               
-                               console.warn("should edit");
-                       }
-               },
-               
-               /**
-                * Sets or updates the code's header title.
-                *
-                * @param       {Element}       pre     code element
-                * @protected
-                */
-               _setTitle: function(pre) {
-                       ['title', 'description'].forEach(function(title) {
-                               var phrase = Language.get('wcf.editor.html.' + title);
-                               
-                               if (elData(pre, title) !== phrase) {
-                                       elData(pre, title, phrase);
-                               }
-                       });
-               },
-               
-               _delete: function (event) {
-                       console.warn("should delete");
-                       event.preventDefault();
-                       
-                       var caretEnd = this._pre.nextElementSibling || this._pre.previousElementSibling;
-                       if (caretEnd === null && this._pre.parentNode !== this._editor.core.editor()[0]) {
-                               caretEnd = this._pre.parentNode;
-                       }
-                       
-                       if (caretEnd === null) {
-                               this._editor.code.set('');
-                               this._editor.focus.end();
-                       }
-                       else {
-                               elRemove(this._pre);
-                               this._editor.caret.end(caretEnd);
-                       }
-                       
-                       UiDialog.close(this);
-               }
-       };
-       
-       return UiRedactorHtml;
-});
-define('WoltLabSuite/Core/Ui/Redactor/Link',['Core', 'EventKey', 'Language', 'Ui/Dialog'], function(Core, EventKey, Language, UiDialog) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       showDialog: function() {},
-                       _submit: function() {},
-                       _dialogSetup: function() {}
-               };
-               return Fake;
-       }
-       
-       var _boundListener = false;
-       var _callback = null;
-       
-       return {
-               showDialog: function(options) {
-                       UiDialog.open(this);
-                       
-                       UiDialog.setTitle(this, Language.get('wcf.editor.link.' + (options.insert ? 'add' : 'edit')));
-                       
-                       var submitButton = elById('redactor-modal-button-action');
-                       submitButton.textContent = Language.get('wcf.global.button.' + (options.insert ? 'insert' : 'save'));
-                       
-                       _callback = options.submitCallback;
-                       
-                       if (!_boundListener) {
-                               _boundListener = true;
-                               
-                               submitButton.addEventListener(WCF_CLICK_EVENT, this._submit.bind(this));
-                       }
-               },
-               
-               _submit: function() {
-                       if (_callback()) {
-                               UiDialog.close(this);
-                       }
-                       else {
-                               var url = elById('redactor-link-url');
-                               elInnerError(url, Language.get((url.value.trim() === '' ? 'wcf.global.form.error.empty' : 'wcf.editor.link.error.invalid')));
-                       }
-               },
-               
-               _dialogSetup: function() {
-                       return {
-                               id: 'redactorDialogLink',
-                               options: {
-                                       onClose: function() {
-                                               var url = elById('redactor-link-url');
-                                               var small = (url.nextElementSibling && url.nextElementSibling.nodeName === 'SMALL') ? url.nextElementSibling : null;
-                                               if (small !== null) {
-                                                       elRemove(small);
-                                               }
-                                       },
-                                       onSetup: function (content) {
-                                               var submitButton = elBySel('.formSubmit > .buttonPrimary', content);
-                                               
-                                               if (submitButton !== null) {
-                                                       elBySelAll('input[type="url"], input[type="text"]', content, function (input) {
-                                                               input.addEventListener('keyup', function (event) {
-                                                                       if (EventKey.Enter(event)) {
-                                                                               Core.triggerEvent(submitButton, 'click');
-                                                                       }
-                                                               });
-                                                       });
-                                               }
-                                       },
-                                       onShow: function () {
-                                               elById('redactor-link-url').focus();
-                                       }
-                               },
-                               source: '<dl>'
-                                               + '<dt><label for="redactor-link-url">' + Language.get('wcf.editor.link.url') + '</label></dt>'
-                                               + '<dd><input type="url" id="redactor-link-url" class="long"></dd>'
-                                       + '</dl>'
-                                       + '<dl>'
-                                               + '<dt><label for="redactor-link-url-text">' + Language.get('wcf.editor.link.text') + '</label></dt>'
-                                               + '<dd><input type="text" id="redactor-link-url-text" class="long"></dd>'
-                                       + '</dl>'
-                                       + '<div class="formSubmit">'
-                                               + '<button id="redactor-modal-button-action" class="buttonPrimary"></button>'
-                                       + '</div>'
-                       };
-               }
-       };
-});
-
-define('WoltLabSuite/Core/Ui/Redactor/Mention',['Ajax', 'Environment', 'StringUtil', 'Ui/CloseOverlay'], function(Ajax, Environment, StringUtil, UiCloseOverlay) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _keyDown: function() {},
-                       _keyUp: function() {},
-                       _getTextLineInFrontOfCaret: function() {},
-                       _getDropdownMenuPosition: function() {},
-                       _setUsername: function() {},
-                       _selectMention: function() {},
-                       _updateDropdownPosition: function() {},
-                       _selectItem: function() {},
-                       _hideDropdown: function() {},
-                       _ajaxSetup: function() {},
-                       _ajaxSuccess: function() {}
-               };
-               return Fake;
-       }
-       
-       var _dropdownContainer = null;
-       
-       function UiRedactorMention(redactor) { this.init(redactor); }
-       UiRedactorMention.prototype = {
-               init: function(redactor) {
-                       this._active = false;
-                       this._dropdownActive = false;
-                       this._dropdownMenu = null;
-                       this._itemIndex = 0;
-                       this._lineHeight = null;
-                       this._mentionStart = '';
-                       this._redactor = redactor;
-                       this._timer = null;
-                       
-                       redactor.WoltLabEvent.register('keydown', this._keyDown.bind(this));
-                       redactor.WoltLabEvent.register('keyup', this._keyUp.bind(this));
-                       
-                       UiCloseOverlay.add('UiRedactorMention-' + redactor.core.element()[0].id, this._hideDropdown.bind(this));
-               },
-               
-               _keyDown: function(data) {
-                       if (!this._dropdownActive) {
-                               return;
-                       }
-                       
-                       /** @var Event event */
-                       var event = data.event;
-                       
-                       switch (event.which) {
-                               // enter
-                               case 13:
-                                       this._setUsername(null, this._dropdownMenu.children[this._itemIndex].children[0]);
-                                       break;
-                               
-                               // arrow up
-                               case 38:
-                                       this._selectItem(-1);
-                                       break;
-                               
-                               // arrow down
-                               case 40:
-                                       this._selectItem(1);
-                                       break;
-                               
-                               default:
-                                       this._hideDropdown();
-                                       return;
-                                       break;
-                       }
-                       
-                       event.preventDefault();
-                       data.cancel = true;
-               },
-               
-               _keyUp: function(data) {
-                       /** @var Event event */
-                       var event = data.event;
-                       
-                       // ignore return key
-                       if (event.which === 13) {
-                               this._active = false;
-                               
-                               return;
-                       }
-                       
-                       if (this._dropdownActive) {
-                               data.cancel = true;
-                               
-                               // ignore arrow up/down
-                               if (event.which === 38 || event.which === 40) {
-                                       return;
-                               }
-                       }
-                       
-                       var text = this._getTextLineInFrontOfCaret();
-                       if (text.length > 0 && text.length < 25) {
-                               var match = text.match(/@([^,]{3,})$/);
-                               if (match) {
-                                       // if mentioning is at text begin or there's a whitespace character
-                                       // before the '@', everything is fine
-                                       if (!match.index || text[match.index - 1].match(/\s/)) {
-                                               this._mentionStart = match[1];
-                                               
-                                               if (this._timer !== null) {
-                                                       window.clearTimeout(this._timer);
-                                                       this._timer = null;
-                                               }
-                                               
-                                               this._timer = window.setTimeout((function() {
-                                                       Ajax.api(this, {
-                                                               parameters: {
-                                                                       data: {
-                                                                               searchString: this._mentionStart
-                                                                       }
-                                                               }
-                                                       });
-                                                       
-                                                       this._timer = null;
-                                               }).bind(this), 500);
-                                       }
-                               }
-                               else {
-                                       this._hideDropdown();
-                               }
-                       }
-                       else {
-                               this._hideDropdown();
-                       }
-               },
-               
-               _getTextLineInFrontOfCaret: function() {
-                       var data = this._selectMention(false);
-                       if (data !== null) {
-                               return data.range.cloneContents().textContent.replace(/\u200B/g, '').replace(/\u00A0/g, ' ').trim();
-                       }
-                       
-                       return '';
-               },
-               
-               _getDropdownMenuPosition: function() {
-                       var data = this._selectMention();
-                       if (data === null) {
-                               return null;
-                       }
-                       
-                       this._redactor.selection.save();
-                       
-                       data.selection.removeAllRanges();
-                       data.selection.addRange(data.range);
-                       
-                       // get the offsets of the bounding box of current text selection
-                       var rect = data.selection.getRangeAt(0).getBoundingClientRect();
-                       var offsets = {
-                               top: Math.round(rect.bottom) + (window.scrollY || window.pageYOffset),
-                               left: Math.round(rect.left) + document.body.scrollLeft
-                       };
-                       
-                       if (this._lineHeight === null) {
-                               this._lineHeight = Math.round(rect.bottom - rect.top);
-                       }
-                       
-                       // restore caret position
-                       this._redactor.selection.restore();
-                       
-                       return offsets;
-               },
-               
-               _setUsername: function(event, item) {
-                       if (event) {
-                               event.preventDefault();
-                               item = event.currentTarget;
-                       }
-                       
-                       var data = this._selectMention();
-                       if (data === null) {
-                               this._hideDropdown();
-                               
-                               return;
-                       }
-                       
-                       // allow redactor to undo this
-                       this._redactor.buffer.set();
-                       
-                       data.selection.removeAllRanges();
-                       data.selection.addRange(data.range);
-                       
-                       var range = getSelection().getRangeAt(0);
-                       range.deleteContents();
-                       range.collapse(true);
-                       
-                       var text = document.createTextNode('@' + elData(item, 'username') + '\u00A0');
-                       range.insertNode(text);
-                       
-                       range = document.createRange();
-                       range.selectNode(text);
-                       range.collapse(false);
-                       
-                       data.selection.removeAllRanges();
-                       data.selection.addRange(range);
-                       
-                       this._hideDropdown();
-               },
-               
-               _selectMention: function (skipCheck) {
-                       var selection = window.getSelection();
-                       if (!selection.rangeCount || !selection.isCollapsed) {
-                               return null;
-                       }
-                       
-                       var container = selection.anchorNode;
-                       if (container.nodeType === Node.TEXT_NODE) {
-                               // work-around for Firefox after suggestions have been presented
-                               container = container.parentNode;
-                       }
-                       
-                       // check if there is an '@' within the current range
-                       if (container.textContent.indexOf('@') === -1) {
-                               return null;
-                       }
-                       
-                       // check if we're inside code or quote blocks
-                       var editor = this._redactor.core.editor()[0];
-                       while (container && container !== editor) {
-                               if (['PRE', 'WOLTLAB-QUOTE'].indexOf(container.nodeName) !== -1) {
-                                       return null;
-                               }
-                               
-                               container = container.parentNode;
-                       }
-                       
-                       var range = selection.getRangeAt(0);
-                       var endContainer = range.startContainer;
-                       var endOffset = range.startOffset;
-                       
-                       // find the appropriate end location
-                       while (endContainer.nodeType === Node.ELEMENT_NODE) {
-                               if (endOffset === 0 && endContainer.childNodes.length === 0) {
-                                       // invalid start location
-                                       return null;
-                               }
-                               
-                               // startOffset for elements will always be after a node index
-                               // or at the very start, which means if there is only text node
-                               // and the caret is after it, startOffset will equal `1`
-                               endContainer = endContainer.childNodes[(endOffset ? endOffset - 1 : 0)];
-                               if (endOffset > 0) {
-                                       if (endContainer.nodeType === Node.TEXT_NODE) {
-                                               endOffset = endContainer.textContent.length;
-                                       }
-                                       else {
-                                               endOffset = endContainer.childNodes.length;
-                                       }
-                               }
-                       }
-                       
-                       var startContainer = endContainer;
-                       var startOffset = -1;
-                       while (startContainer !== null) {
-                               if (startContainer.nodeType !== Node.TEXT_NODE) {
-                                       return null;
-                               }
-                               
-                               if (startContainer.textContent.indexOf('@') !== -1) {
-                                       startOffset = startContainer.textContent.lastIndexOf('@');
-                                       
-                                       break;
-                               }
-                               
-                               startContainer = startContainer.previousSibling;
-                       }
-                       
-                       if (startOffset === -1) {
-                               // there was a non-text node that was in our way
-                               return null;
-                       }
-                       
-                       try {
-                               // mark the entire text, starting from the '@' to the current cursor position
-                               range = document.createRange();
-                               range.setStart(startContainer, startOffset);
-                               range.setEnd(endContainer, endOffset);
-                       }
-                       catch (e) {
-                               window.console.debug(e);
-                               return null;
-                       }
-                       
-                       if (skipCheck === false) {
-                               // check if the `@` occurs at the very start of the container
-                               // or at least has a whitespace in front of it
-                               var text = '';
-                               if (startOffset) {
-                                       text = startContainer.textContent.substr(0, startOffset);
-                               }
-                               
-                               while (startContainer = startContainer.previousSibling) {
-                                       if (startContainer.nodeType === Node.TEXT_NODE) {
-                                               text = startContainer.textContent + text;
-                                       }
-                                       else {
-                                               break;
-                                       }
-                               }
-                               
-                               if (text.replace(/\u200B/g, '').match(/\S$/)) {
-                                       return null;
-                               }
-                       }
-                       else {
-                               // check if new range includes the mention text
-                               if (range.cloneContents().textContent.replace(/\u200B/g, '').replace(/\u00A0/g, '').trim().replace(/^@/, '') !== this._mentionStart) {
-                                       // string mismatch
-                                       return null;
-                               }
-                       }
-                       
-                       return {
-                               range: range,
-                               selection: selection
-                       };
-               },
-               
-               _updateDropdownPosition: function() {
-                       var offset = this._getDropdownMenuPosition();
-                       if (offset === null) {
-                               this._hideDropdown();
-                               
-                               return;
-                       }
-                       offset.top += 7; // add a little vertical gap
-                       
-                       this._dropdownMenu.style.setProperty('left', offset.left + 'px', '');
-                       this._dropdownMenu.style.setProperty('top', offset.top + 'px', '');
-                       
-                       this._selectItem(0);
-                       
-                       if (offset.top + this._dropdownMenu.offsetHeight + 10 > window.innerHeight + (window.scrollY || window.pageYOffset)) {
-                               this._dropdownMenu.style.setProperty('top', offset.top - this._dropdownMenu.offsetHeight - 2 * this._lineHeight + 7 + 'px', '');
-                       }
-               },
-               
-               _selectItem: function(step) {
-                       // find currently active item
-                       var item = elBySel('.active', this._dropdownMenu);
-                       if (item !== null) {
-                               item.classList.remove('active');
-                       }
-                       
-                       this._itemIndex += step;
-                       if (this._itemIndex < 0) {
-                               this._itemIndex = this._dropdownMenu.childElementCount - 1;
-                       }
-                       else if (this._itemIndex >= this._dropdownMenu.childElementCount) {
-                               this._itemIndex = 0;
-                       }
-                       
-                       this._dropdownMenu.children[this._itemIndex].classList.add('active');
-               },
-               
-               _hideDropdown: function() {
-                       if (this._dropdownMenu !== null) this._dropdownMenu.classList.remove('dropdownOpen');
-                       this._dropdownActive = false;
-                       this._itemIndex = 0;
-               },
-               
-               _ajaxSetup: function() {
-                       return {
-                               data: {
-                                       actionName: 'getSearchResultList',
-                                       className: 'wcf\\data\\user\\UserAction',
-                                       interfaceName: 'wcf\\data\\ISearchAction',
-                                       parameters: {
-                                               data: {
-                                                       includeUserGroups: true,
-                                                       scope: 'mention'
-                                               }
-                                       }
-                               },
-                               silent: true
-                       };
-               },
-               
-               _ajaxSuccess: function(data) {
-                       if (!Array.isArray(data.returnValues) || !data.returnValues.length) {
-                               this._hideDropdown();
-                               
-                               return;
-                       }
-                       
-                       if (this._dropdownMenu === null) {
-                               this._dropdownMenu = elCreate('ol');
-                               this._dropdownMenu.className = 'dropdownMenu';
-                               
-                               if (_dropdownContainer === null) {
-                                       _dropdownContainer = elCreate('div');
-                                       _dropdownContainer.className = 'dropdownMenuContainer';
-                                       document.body.appendChild(_dropdownContainer);
-                               }
-                               
-                               _dropdownContainer.appendChild(this._dropdownMenu);
-                       }
-                       
-                       this._dropdownMenu.innerHTML = '';
-                       
-                       var callbackClick = this._setUsername.bind(this), link, listItem, user;
-                       for (var i = 0, length = data.returnValues.length; i < length; i++) {
-                               user = data.returnValues[i];
-                               
-                               listItem = elCreate('li');
-                               link = elCreate('a');
-                               link.addEventListener('mousedown', callbackClick);
-                               link.className = 'box16';
-                               link.innerHTML = '<span>' + user.icon + '</span> <span>' + StringUtil.escapeHTML(user.label) + '</span>';
-                               elData(link, 'user-id', user.objectID);
-                               elData(link, 'username', user.label);
-                               
-                               listItem.appendChild(link);
-                               this._dropdownMenu.appendChild(listItem);
-                       }
-                       
-                       this._dropdownMenu.classList.add('dropdownOpen');
-                       this._dropdownActive = true;
-                       
-                       this._updateDropdownPosition();
-               }
-       };
-       
-       return UiRedactorMention;
-});
-
-/**
- * Converts `<woltlab-metacode>` into the bbcode representation.
- *
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Redactor/Page
- */
-define('WoltLabSuite/Core/Ui/Redactor/Page',['WoltLabSuite/Core/Ui/Page/Search'], function(UiPageSearch) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _click: function() {},
-                       _insert: function() {}
-               };
-               return Fake;
-       }
-       
-       function UiRedactorPage(editor, button) { this.init(editor, button); }
-       UiRedactorPage.prototype = {
-               init: function (editor, button) {
-                       this._editor = editor;
-                       
-                       button.addEventListener(WCF_CLICK_EVENT, this._click.bind(this));
-               },
-               
-               _click: function (event) {
-                       event.preventDefault();
-                       
-                       UiPageSearch.open(this._insert.bind(this));
-               },
-               
-               _insert: function (pageID) {
-                       this._editor.buffer.set();
-                       
-                       this._editor.insert.text("[wsp='" + pageID + "'][/wsp]");
-               }
-       };
-       
-       return UiRedactorPage;
-});
-
-/**
- * Manages quotes.
- *
- * @author      Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module      WoltLabSuite/Core/Ui/Redactor/Quote
- */
-define('WoltLabSuite/Core/Ui/Redactor/Quote',['Core', 'EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog', './Metacode', './PseudoHeader'], function (Core, EventHandler, EventKey, Language, StringUtil, DomUtil, UiDialog, UiRedactorMetacode, UiRedactorPseudoHeader) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _insertQuote: function() {},
-                       _click: function() {},
-                       _observeLoad: function() {},
-                       _edit: function() {},
-                       _save: function() {},
-                       _setTitle: function() {},
-                       _delete: function() {},
-                       _dialogSetup: function() {},
-                       _dialogSubmit: function() {}
-               };
-               return Fake;
-       }
-       
-       var _headerHeight = 0;
-       
-       /**
-        * @param       {Object}        editor  editor instance
-        * @param       {jQuery}        button  toolbar button
-        * @constructor
-        */
-       function UiRedactorQuote(editor, button) { this.init(editor, button); }
-       UiRedactorQuote.prototype = {
-               /**
-                * Initializes the quote management.
-                * 
-                * @param       {Object}        editor  editor instance
-                * @param       {jQuery}        button  toolbar button
-                */
-               init: function(editor, button) {
-                       this._quote = null;
-                       this._quotes = elByTag('woltlab-quote', editor.$editor[0]);
-                       this._editor = editor;
-                       this._elementId = this._editor.$element[0].id;
-                       
-                       EventHandler.add('com.woltlab.wcf.redactor2', 'observe_load_' + this._elementId, this._observeLoad.bind(this));
-                       
-                       this._editor.button.addCallback(button, this._click.bind(this));
-                       
-                       // static bind to ensure that removing works
-                       this._callbackEdit = this._edit.bind(this);
-                       
-                       // bind listeners on init
-                       this._observeLoad();
-                       
-                       // quote manager
-                       EventHandler.add('com.woltlab.wcf.redactor2', 'insertQuote_' + this._elementId, this._insertQuote.bind(this));
-               },
-               
-               /**
-                * Inserts a quote.
-                * 
-                * @param       {Object}        data            quote data
-                * @protected
-                */
-               _insertQuote: function (data) {
-                       if (this._editor.WoltLabSource.isActive()) {
-                               return;
-                       }
-                       
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'showEditor');
-                       
-                       var editor = this._editor.core.editor()[0];
-                       this._editor.selection.restore();
-                       
-                       this._editor.buffer.set();
-                       
-                       // caret must be within a `<p>`, if it is not: move it
-                       var block = this._editor.selection.block();
-                       if (block === false) {
-                               this._editor.focus.end();
-                               block = this._editor.selection.block();
-                       }
-                       
-                       while (block && block.parentNode !== editor) {
-                               block = block.parentNode;
-                       }
-                       
-                       var quote = elCreate('woltlab-quote');
-                       elData(quote, 'author', data.author);
-                       elData(quote, 'link', data.link);
-                       
-                       var content = data.content;
-                       if (data.isText) {
-                               content = StringUtil.escapeHTML(content);
-                               content = '<p>' + content + '</p>';
-                               content = content.replace(/\n\n/g, '</p><p>');
-                               content = content.replace(/\n/g, '<br>');
-                       }
-                       else {
-                               //noinspection JSUnresolvedFunction
-                               content = UiRedactorMetacode.convertFromHtml(this._editor.$element[0].id, content);
-                       }
-                       
-                       // bypass the editor as `insert.html()` doesn't like us
-                       quote.innerHTML = content;
-                       
-                       block.parentNode.insertBefore(quote, block.nextSibling);
-                       
-                       if (block.nodeName === 'P' && (block.innerHTML === '<br>' || block.innerHTML.replace(/\u200B/g, '') === '')) {
-                               block.parentNode.removeChild(block);
-                       }
-                       
-                       // avoid adjacent blocks that are not paragraphs
-                       var sibling = quote.previousElementSibling;
-                       if (sibling && sibling.nodeName !== 'P') {
-                               sibling = elCreate('p');
-                               sibling.textContent = '\u200B';
-                               quote.parentNode.insertBefore(sibling, quote);
-                       }
-                       
-                       this._editor.WoltLabCaret.paragraphAfterBlock(quote);
-                       
-                       this._editor.buffer.set();
-               },
-               
-               /**
-                * Toggles the quote block on button click.
-                * 
-                * @protected
-                */
-               _click: function() {
-                       this._editor.button.toggle({}, 'woltlab-quote', 'func', 'block.format');
-                       
-                       var quote = this._editor.selection.block();
-                       if (quote && quote.nodeName === 'WOLTLAB-QUOTE') {
-                               this._setTitle(quote);
-                               
-                               quote.addEventListener(WCF_CLICK_EVENT, this._callbackEdit);
-                               
-                               // work-around for Safari
-                               this._editor.caret.end(quote);
-                       }
-               },
-               
-               /**
-                * Binds event listeners and sets quote title on both editor
-                * initialization and when switching back from code view.
-                * 
-                * @protected
-                */
-               _observeLoad: function() {
-                       var quote;
-                       for (var i = 0, length = this._quotes.length; i < length; i++) {
-                               quote = this._quotes[i];
-                               
-                               quote.addEventListener('mousedown', this._callbackEdit);
-                               this._setTitle(quote);
-                       }
-               },
-               
-               /**
-                * Opens the dialog overlay to edit the quote's properties.
-                * 
-                * @param       {Event}         event           event object
-                * @protected
-                */
-               _edit: function(event) {
-                       var quote = event.currentTarget;
-                       
-                       if (_headerHeight === 0) {
-                               _headerHeight = UiRedactorPseudoHeader.getHeight(quote);
-                       }
-                       
-                       // check if the click hit the header
-                       var offset = DomUtil.offset(quote);
-                       if (event.pageY > offset.top && event.pageY < (offset.top + _headerHeight)) {
-                               event.preventDefault();
-                               
-                               this._editor.selection.save();
-                               this._quote = quote;
-                               
-                               UiDialog.open(this);
-                       }
-               },
-               
-               /**
-                * Saves the changes to the quote's properties.
-                * 
-                * @protected
-                */
-               _dialogSubmit: function() {
-                       var id = 'redactor-quote-' + this._elementId;
-                       var urlInput = elById(id + '-url');
-                       
-                       var url = urlInput.value.replace(/\u200B/g, '').trim();
-                       // simple test to check if it at least looks like it could be a valid url
-                       if (url.length && !/^https?:\/\/[^\/]+/.test(url)) {
-                               elInnerError(urlInput, Language.get('wcf.editor.quote.url.error.invalid'));
-                               return;
-                       }
-                       else {
-                               elInnerError(urlInput, false);
-                       }
-                       
-                       // set author
-                       elData(this._quote, 'author', elById(id + '-author').value);
-                       
-                       // set url
-                       elData(this._quote, 'link', url);
-                       
-                       this._setTitle(this._quote);
-                       this._editor.caret.after(this._quote);
-                       
-                       UiDialog.close(this);
-               },
-               
-               /**
-                * Sets or updates the quote's header title.
-                * 
-                * @param       {Element}       quote     quote element
-                * @protected
-                */
-               _setTitle: function(quote) {
-                       var title = Language.get('wcf.editor.quote.title', {
-                               author: elData(quote, 'author'),
-                               url: elData(quote, 'url')
-                       });
-                       
-                       if (elData(quote, 'title') !== title) {
-                               elData(quote, 'title', title);
-                       }
-               },
-               
-               _delete: function (event) {
-                       event.preventDefault();
-                       
-                       var caretEnd = this._quote.nextElementSibling || this._quote.previousElementSibling;
-                       if (caretEnd === null && this._quote.parentNode !== this._editor.core.editor()[0]) {
-                               caretEnd = this._quote.parentNode;
-                       }
-                       
-                       if (caretEnd === null) {
-                               this._editor.code.set('');
-                               this._editor.focus.end();
-                       }
-                       else {
-                               elRemove(this._quote);
-                               this._editor.caret.end(caretEnd);
-                       }
-                       
-                       UiDialog.close(this);
-               },
-               
-               _dialogSetup: function() {
-                       var id = 'redactor-quote-' + this._elementId,
-                           idAuthor = id + '-author',
-                           idButtonDelete = id + '-button-delete',
-                           idButtonSave = id + '-button-save',
-                           idUrl = id + '-url';
-                       
-                       return {
-                               id: id,
-                               options: {
-                                       onClose: (function () {
-                                               this._editor.selection.restore();
-                                               
-                                               UiDialog.destroy(this);
-                                       }).bind(this),
-                                       
-                                       onSetup: (function() {
-                                               elById(idButtonDelete).addEventListener(WCF_CLICK_EVENT, this._delete.bind(this));
-                                       }).bind(this),
-                                       
-                                       onShow: (function() {
-                                               elById(idAuthor).value = elData(this._quote, 'author');
-                                               elById(idUrl).value = elData(this._quote, 'link');
-                                       }).bind(this),
-                                       
-                                       title: Language.get('wcf.editor.quote.edit')
-                               },
-                               source: '<div class="section">'
-                                       + '<dl>'
-                                               + '<dt><label for="' + idAuthor + '">' + Language.get('wcf.editor.quote.author') + '</label></dt>'
-                                               + '<dd>'
-                                                       + '<input type="text" id="' + idAuthor + '" class="long" data-dialog-submit-on-enter="true">'
-                                               + '</dd>'
-                                       + '</dl>'
-                                       + '<dl>'
-                                               + '<dt><label for="' + idUrl + '">' + Language.get('wcf.editor.quote.url') + '</label></dt>'
-                                               + '<dd>'
-                                                       + '<input type="text" id="' + idUrl + '" class="long" data-dialog-submit-on-enter="true">'
-                                                       + '<small>' + Language.get('wcf.editor.quote.url.description') + '</small>'
-                                               + '</dd>'
-                                       + '</dl>'
-                               + '</div>'
-                               + '<div class="formSubmit">'
-                                       + '<button id="' + idButtonSave + '" class="buttonPrimary" data-type="submit">' + Language.get('wcf.global.button.save') + '</button>'
-                                       + '<button id="' + idButtonDelete + '">' + Language.get('wcf.global.button.delete') + '</button>'
-                               + '</div>'
-                       };
-               }
-       };
-       
-       return UiRedactorQuote;
-});
-/**
- * Manages spoilers.
- *
- * @author      Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module      WoltLabSuite/Core/Ui/Redactor/Spoiler
- */
-define('WoltLabSuite/Core/Ui/Redactor/Spoiler',['EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog', './PseudoHeader'], function (EventHandler, EventKey, Language, StringUtil, DomUtil, UiDialog, UiRedactorPseudoHeader) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _bbcodeSpoiler: function() {},
-                       _observeLoad: function() {},
-                       _edit: function() {},
-                       _setTitle: function() {},
-                       _delete: function() {},
-                       _dialogSetup: function() {},
-                       _dialogSubmit: function() {}
-               };
-               return Fake;
-       }
-       
-       var _headerHeight = 0;
-       
-       /**
-        * @param       {Object}        editor  editor instance
-        * @constructor
-        */
-       function UiRedactorSpoiler(editor) { this.init(editor); }
-       UiRedactorSpoiler.prototype = {
-               /**
-                * Initializes the spoiler management.
-                * 
-                * @param       {Object}        editor  editor instance
-                */
-               init: function(editor) {
-                       this._editor = editor;
-                       this._elementId = this._editor.$element[0].id;
-                       this._spoiler = null;
-                       
-                       EventHandler.add('com.woltlab.wcf.redactor2', 'bbcode_spoiler_' + this._elementId, this._bbcodeSpoiler.bind(this));
-                       EventHandler.add('com.woltlab.wcf.redactor2', 'observe_load_' + this._elementId, this._observeLoad.bind(this));
-                       
-                       // static bind to ensure that removing works
-                       this._callbackEdit = this._edit.bind(this);
-                       
-                       // bind listeners on init
-                       this._observeLoad();
-               },
-               
-               /**
-                * Intercepts the insertion of `[spoiler]` tags and uses
-                * the custom `<woltlab-spoiler>` element instead.
-                * 
-                * @param       {Object}        data    event data
-                * @protected
-                */
-               _bbcodeSpoiler: function(data) {
-                       data.cancel = true;
-                       
-                       this._editor.button.toggle({}, 'woltlab-spoiler', 'func', 'block.format');
-                       
-                       var spoiler = this._editor.selection.block();
-                       if (spoiler && spoiler.nodeName === 'WOLTLAB-SPOILER') {
-                               this._setTitle(spoiler);
-                               
-                               spoiler.addEventListener(WCF_CLICK_EVENT, this._callbackEdit);
-                               
-                               // work-around for Safari
-                               this._editor.caret.end(spoiler);
-                       }
-               },
-               
-               /**
-                * Binds event listeners and sets quote title on both editor
-                * initialization and when switching back from code view.
-                * 
-                * @protected
-                */
-               _observeLoad: function() {
-                       elBySelAll('woltlab-spoiler', this._editor.$editor[0], (function(spoiler) {
-                               spoiler.addEventListener('mousedown', this._callbackEdit);
-                               this._setTitle(spoiler);
-                       }).bind(this));
-               },
-               
-               /**
-                * Opens the dialog overlay to edit the spoiler's properties.
-                * 
-                * @param       {Event}         event           event object
-                * @protected
-                */
-               _edit: function(event) {
-                       var spoiler = event.currentTarget;
-                       
-                       if (_headerHeight === 0) {
-                               _headerHeight = UiRedactorPseudoHeader.getHeight(spoiler);
-                       }
-                       
-                       // check if the click hit the header
-                       var offset = DomUtil.offset(spoiler);
-                       if (event.pageY > offset.top && event.pageY < (offset.top + _headerHeight)) {
-                               event.preventDefault();
-                               
-                               this._editor.selection.save();
-                               this._spoiler = spoiler;
-                               
-                               UiDialog.open(this);
-                       }
-               },
-               
-               /**
-                * Saves the changes to the spoiler's properties.
-                * 
-                * @protected
-                */
-               _dialogSubmit: function() {
-                       elData(this._spoiler, 'label', elById('redactor-spoiler-' + this._elementId + '-label').value);
-                       
-                       this._setTitle(this._spoiler);
-                       this._editor.caret.after(this._spoiler);
-                       
-                       UiDialog.close(this);
-               },
-               
-               /**
-                * Sets or updates the spoiler's header title.
-                * 
-                * @param       {Element}       spoiler     spoiler element
-                * @protected
-                */
-               _setTitle: function(spoiler) {
-                       var title = Language.get('wcf.editor.spoiler.title', { label: elData(spoiler, 'label') });
-                       
-                       if (elData(spoiler, 'title') !== title) {
-                               elData(spoiler, 'title', title);
-                       }
-               },
-               
-               _delete: function (event) {
-                       event.preventDefault();
-                       
-                       var caretEnd = this._spoiler.nextElementSibling || this._spoiler.previousElementSibling;
-                       if (caretEnd === null && this._spoiler.parentNode !== this._editor.core.editor()[0]) {
-                               caretEnd = this._spoiler.parentNode;
-                       }
-                       
-                       if (caretEnd === null) {
-                               this._editor.code.set('');
-                               this._editor.focus.end();
-                       }
-                       else {
-                               elRemove(this._spoiler);
-                               this._editor.caret.end(caretEnd);
-                       }
-                       
-                       UiDialog.close(this);
-               },
-               
-               _dialogSetup: function() {
-                       var id = 'redactor-spoiler-' + this._elementId,
-                           idButtonDelete = id + '-button-delete',
-                           idButtonSave = id + '-button-save',
-                           idLabel = id + '-label';
-                       
-                       return {
-                               id: id,
-                               options: {
-                                       onClose: (function () {
-                                               this._editor.selection.restore();
-                                               
-                                               UiDialog.destroy(this);
-                                       }).bind(this),
-                                       
-                                       onSetup: (function() {
-                                               elById(idButtonDelete).addEventListener(WCF_CLICK_EVENT, this._delete.bind(this));
-                                       }).bind(this),
-                                       
-                                       onShow: (function() {
-                                               elById(idLabel).value = elData(this._spoiler, 'label');
-                                       }).bind(this),
-                                       
-                                       title: Language.get('wcf.editor.spoiler.edit')
-                               },
-                               source: '<div class="section">'
-                                       + '<dl>'
-                                               + '<dt><label for="' + idLabel + '">' + Language.get('wcf.editor.spoiler.label') + '</label></dt>'
-                                               + '<dd>'
-                                                       + '<input type="text" id="' + idLabel + '" class="long" data-dialog-submit-on-enter="true">'
-                                                       + '<small>' + Language.get('wcf.editor.spoiler.label.description') + '</small>'
-                                               + '</dd>'
-                                       + '</dl>'
-                               + '</div>'
-                               + '<div class="formSubmit">'
-                                       + '<button id="' + idButtonSave + '" class="buttonPrimary" data-type="submit">' + Language.get('wcf.global.button.save') + '</button>'
-                                       + '<button id="' + idButtonDelete + '">' + Language.get('wcf.global.button.delete') + '</button>'
-                               + '</div>'
-                       };
-               }
-       };
-       
-       return UiRedactorSpoiler;
-});
-define('WoltLabSuite/Core/Ui/Redactor/Table',['Language', 'Ui/Dialog'], function(Language, UiDialog) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       showDialog: function() {},
-                       _submit: function() {},
-                       _dialogSetup: function() {}
-               };
-               return Fake;
-       }
-       
-       var _callback = null;
-       
-       return {
-               showDialog: function(options) {
-                       UiDialog.open(this);
-                       
-                       _callback = options.submitCallback;
-               },
-               
-               _dialogSubmit: function() {
-                       // check if rows and cols are within the boundaries
-                       var isValid = true;
-                       ['rows', 'cols'].forEach(function(type) {
-                               var input = elById('redactor-table-' + type);
-                               if (input.value < 1 || input.value > 100) {
-                                       isValid = false;
-                               }
-                       });
-                       
-                       if (!isValid) return;
-                       
-                       _callback();
-                       
-                       UiDialog.close(this);
-               },
-               
-               _dialogSetup: function() {
-                       return {
-                               id: 'redactorDialogTable',
-                               options: {
-                                       onShow: function () {
-                                               elById('redactor-table-rows').value = 2;
-                                               elById('redactor-table-cols').value = 3;
-                                       },
-                                       title: Language.get('wcf.editor.table.insertTable')
-                               },
-                               source: '<dl>'
-                                               + '<dt><label for="redactor-table-rows">' + Language.get('wcf.editor.table.rows') + '</label></dt>'
-                                               + '<dd><input type="number" id="redactor-table-rows" class="small" min="1" max="100" value="2" data-dialog-submit-on-enter="true"></dd>'
-                                       + '</dl>'
-                                       + '<dl>'
-                                               + '<dt><label for="redactor-table-cols">' + Language.get('wcf.editor.table.cols') + '</label></dt>'
-                                               + '<dd><input type="number" id="redactor-table-cols" class="small" min="1" max="100" value="3" data-dialog-submit-on-enter="true"></dd>'
-                                       + '</dl>'
-                                       + '<div class="formSubmit">'
-                                               + '<button id="redactor-modal-button-action" class="buttonPrimary" data-type="submit">' + Language.get('wcf.global.button.insert') + '</button>'
-                                       + '</div>'
-                       };
-               }
-       };
-});
-
-define('WoltLabSuite/Core/Ui/Search/Page',['Core', 'Dom/Traverse', 'Dom/Util', 'Ui/Screen', 'Ui/SimpleDropdown', './Input'], function(Core, DomTraverse, DomUtil, UiScreen, UiSimpleDropdown, UiSearchInput) {
-       "use strict";
-       
-       return {
-               init: function (objectType) {
-                       var searchInput = elById('pageHeaderSearchInput');
-                       
-                       new UiSearchInput(searchInput, {
-                               ajax: {
-                                       className: 'wcf\\data\\search\\keyword\\SearchKeywordAction'
-                               },
-                               callbackDropdownInit: function(dropdownMenu) {
-                                       dropdownMenu.classList.add('dropdownMenuPageSearch');
-                                       
-                                       if (UiScreen.is('screen-lg')) {
-                                               elData(dropdownMenu, 'dropdown-alignment-horizontal', 'right');
-                                               
-                                               var minWidth = searchInput.clientWidth;
-                                               dropdownMenu.style.setProperty('min-width', minWidth + 'px', '');
-                                               
-                                               // calculate offset to ignore the width caused by the submit button
-                                               var parent = searchInput.parentNode;
-                                               var offsetRight = (DomUtil.offset(parent).left + parent.clientWidth) - (DomUtil.offset(searchInput).left + minWidth);
-                                               var offsetTop = DomUtil.styleAsInt(window.getComputedStyle(parent), 'padding-bottom');
-                                               dropdownMenu.style.setProperty('transform', 'translateX(-' + Math.ceil(offsetRight) + 'px) translateY(-' + offsetTop + 'px)', '');
-                                       }
-                               },
-                               callbackSelect: function() {
-                                       setTimeout(function() {
-                                               DomTraverse.parentByTag(searchInput, 'FORM').submit();
-                                       }, 1);
-                                       
-                                       return true;
-                               }
-                       });
-                       
-                       var dropdownMenu = UiSimpleDropdown.getDropdownMenu(DomUtil.identify(elBySel('.pageHeaderSearchType')));
-                       var callback = this._click.bind(this);
-                       elBySelAll('a[data-object-type]', dropdownMenu, function(link) {
-                               link.addEventListener(WCF_CLICK_EVENT, callback);
-                       });
-                       
-                       // trigger click on init
-                       var link = elBySel('a[data-object-type="' + objectType + '"]', dropdownMenu);
-                       Core.triggerEvent(link, WCF_CLICK_EVENT);
-               },
-               
-               _click: function(event) {
-                       event.preventDefault();
-                       
-                       var pageHeader = elById('pageHeader');
-                       pageHeader.classList.add('searchBarForceOpen');
-                       window.setTimeout(function() {
-                               pageHeader.classList.remove('searchBarForceOpen');
-                       }, 10);
-                       
-                       var objectType = elData(event.currentTarget, 'object-type');
-                       
-                       var container = elById('pageHeaderSearchParameters');
-                       container.innerHTML = '';
-                       
-                       var extendedLink = elData(event.currentTarget, 'extended-link');
-                       if (extendedLink) {
-                               elBySel('.pageHeaderSearchExtendedLink').href = extendedLink;
-                       }
-                       
-                       var parameters = elData(event.currentTarget, 'parameters');
-                       if (parameters) {
-                               parameters = JSON.parse(parameters);
-                       }
-                       else {
-                               parameters = {};
-                       }
-                       
-                       if (objectType) parameters['types[]'] = objectType;
-                       
-                       for (var key in parameters) {
-                               if (parameters.hasOwnProperty(key)) {
-                                       var input = elCreate('input');
-                                       input.type = 'hidden';
-                                       input.name = key;
-                                       input.value = parameters[key];
-                                       container.appendChild(input);
-                               }
-                       }
-                       
-                       // update label
-                       var button = elBySel('.pageHeaderSearchType > .button > .pageHeaderSearchTypeLabel', elById('pageHeaderSearchInputContainer'));
-                       button.textContent = event.currentTarget.textContent;
-               }
-       };
-});
-
-/**
- * Inserts smilies into a WYSIWYG editor instance, with WAI-ARIA keyboard support.
- * 
- * @author      Alexander Ebert
- * @copyright   2001-2019 WoltLab GmbH
- * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module      WoltLabSuite/Core/Ui/Smiley/Insert
- */
-define('WoltLabSuite/Core/Ui/Smiley/Insert',['EventHandler', 'EventKey'], function (EventHandler, EventKey) {
-       'use strict';
-       
-       function UiSmileyInsert(editorId) { this.init(editorId); }
-       
-       UiSmileyInsert.prototype = {
-               _editorId: '',
-               
-               /**
-                * @param {string} editorId
-                */
-               init: function (editorId) {
-                       this._editorId = editorId;
-                       
-                       var container = elById('smilies-' + this._editorId);
-                       if (!container) {
-                               // form builder
-                               container = elById(this._editorId + 'SmiliesTabContainer');
-                               if (!container) {
-                                       throw new Error('Unable to find the message tab menu container containing the smilies.');
-                               }
-                       }
-                       
-                       container.addEventListener('keydown', this._keydown.bind(this));
-                       container.addEventListener('mousedown', this._mousedown.bind(this));
-               },
-               
-               /**
-                * @param {KeyboardEvent} event
-                * @protected
-                */
-               _keydown: function(event) {
-                       var activeButton = document.activeElement;
-                       if (!activeButton.classList.contains('jsSmiley')) {
-                               return;
-                       }
-                       
-                       if (EventKey.ArrowLeft(event) || EventKey.ArrowRight(event) || EventKey.Home(event) || EventKey.End(event)) {
-                               event.preventDefault();
-                               
-                               var smilies = Array.prototype.slice.call(elBySelAll('.jsSmiley', event.currentTarget));
-                               if (EventKey.ArrowLeft(event)) {
-                                       smilies.reverse();
-                               }
-                               
-                               var index = smilies.indexOf(activeButton);
-                               if (EventKey.Home(event)) {
-                                       index = 0;
-                               }
-                               else if (EventKey.End(event)) {
-                                       index = smilies.length - 1;
-                               }
-                               else {
-                                       index = index + 1;
-                                       if (index === smilies.length) {
-                                               index = 0;
-                                       }
-                               }
-                               
-                               smilies[index].focus();
-                       }
-                       else if (EventKey.Enter(event) || EventKey.Space(event)) {
-                               event.preventDefault();
-                               
-                               this._insert(elBySel('img', activeButton));
-                       }
-               },
-               
-               /**
-                * @param {MouseEvent} event
-                * @protected
-                */
-               _mousedown: function (event) {
-                       event.preventDefault();
-                       
-                       // Clicks may occur on a few different elements, but we are only looking for the image.
-                       var listItem = event.target.closest('li');
-                       var img = elBySel('img', listItem);
-                       if (img) this._insert(img);
-               },
-               
-               /**
-                * @param {Element} img
-                * @protected
-                */
-               _insert: function(img) {
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'insertSmiley_' + this._editorId, {
-                               img: img
-                       });
-               }
-       };
-       return UiSmileyInsert;
-});
-
-/**
- * Provides a selection dialog for FontAwesome icons with filter capabilities.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Style/FontAwesome
- */
-define('WoltLabSuite/Core/Ui/Style/FontAwesome',['Language', 'Ui/Dialog', 'WoltLabSuite/Core/Ui/ItemList/Filter'], function (Language, UiDialog, UiItemListFilter) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       setup: function() {},
-                       open: function() {},
-                       _click: function() {},
-                       _dialogSetup: function() {}
-               };
-               return Fake;
-       }
-       
-       var _callback, _iconList, _itemListFilter;
-       var _icons = [];
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Style/FontAwesome
-        */
-       return {
-               /**
-                * Sets the list of available icons, must be invoked prior to any call
-                * to the `open()` method.
-                * 
-                * @param       {string[]}      icons   list of icon names excluding the `fa-` prefix
-                */
-               setup: function (icons) {
-                       _icons = icons;
-               },
-               
-               /**
-                * Shows the FontAwesome selection dialog, supplied callback will be
-                * invoked with the selection icon's name as the only argument.
-                * 
-                * @param       {Function<string>}      callback        callback on icon selection, receives icon name only
-                */
-               open: function(callback) {
-                       if (_icons.length === 0) {
-                               throw new Error("Missing icon data, please include the template before calling this method using `{include file='fontAwesomeJavaScript'}`.");
-                       }
-                       
-                       _callback = callback;
-                       
-                       UiDialog.open(this);
-               },
-               
-               /**
-                * Selects an icon, notifies the callback and closes the dialog.
-                * 
-                * @param       {Event}         event           event object
-                * @protected
-                */
-               _click: function(event) {
-                       event.preventDefault();
-                       
-                       var item = event.target.closest('li');
-                       var icon = elBySel('small', item).textContent.trim();
-                       
-                       UiDialog.close(this);
-                       
-                       _callback(icon);
-               },
-               
-               _dialogSetup: function() {
-                       return {
-                               id: 'fontAwesomeSelection',
-                               options: {
-                                       onSetup: (function() {
-                                               _iconList = elById('fontAwesomeIcons');
-                                               
-                                               // build icons
-                                               var icon, html = '';
-                                               for (var i = 0, length = _icons.length; i < length; i++) {
-                                                       icon = _icons[i];
-                                                       
-                                                       html += '<li><span class="icon icon48 fa-' + icon + '"></span><small>' + icon + '</small></li>';
-                                               }
-                                               
-                                               _iconList.innerHTML = html;
-                                               _iconList.addEventListener(WCF_CLICK_EVENT, this._click.bind(this));
-                                               
-                                               _itemListFilter = new UiItemListFilter('fontAwesomeIcons', {
-                                                       callbackPrepareItem: function (item) {
-                                                               var small = elBySel('small', item);
-                                                               var text = small.textContent.trim();
-                                                               
-                                                               return {
-                                                                       item: item,
-                                                                       span: small,
-                                                                       text: text
-                                                               };
-                                                       },
-                                                       enableVisibilityFilter: false
-                                               });
-                                       }).bind(this),
-                                       onShow: function () {
-                                               _itemListFilter.reset();
-                                       },
-                                       title: Language.get('wcf.global.fontAwesome.selectIcon')
-                               },
-                               source: '<ul class="fontAwesomeIcons" id="fontAwesomeIcons"></ul>'
-                       };
-               }
-       }
-});
-/**
- * Provides a simple toggle to show or hide certain elements when the
- * target element is checked.
- * 
- * Be aware that the list of elements to show or hide accepts selectors
- * which will be passed to `elBySel()`, causing only the first matched
- * element to be used. If you require a whole list of elements identified
- * by a single selector to be handled, please provide the actual list of
- * elements instead.
- * 
- * Usage:
- * 
- * new UiToggleInput('input[name="foo"][value="bar"]', {
- *      show: ['#showThisContainer', '.makeThisVisibleToo'],
- *      hide: ['.notRelevantStuff', elById('fooBar')]
- * });
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Toggle/Input
- */
-define('WoltLabSuite/Core/Ui/Toggle/Input',['Core'], function(Core) {
-       "use strict";
-       
-       /**
-        * @param       {string}        elementSelector         element selector used with `elBySel()`
-        * @param       {Object}        options                 toggle options
-        * @constructor
-        */
-       function UiToggleInput(elementSelector, options) { this.init(elementSelector, options); }
-       UiToggleInput.prototype = {
-               /**
-                * Initializes a new input toggle.
-                * 
-                * @param       {string}        elementSelector         element selector used with `elBySel()`
-                * @param       {Object}        options                 toggle options
-                */
-               init: function(elementSelector, options) {
-                       this._element = elBySel(elementSelector);
-                       if (this._element === null) {
-                               throw new Error("Unable to find element by selector '" + elementSelector + "'.");
-                       }
-                       
-                       var type = (this._element.nodeName === 'INPUT') ? elAttr(this._element, 'type') : '';
-                       if (type !== 'checkbox' && type !== 'radio') {
-                               throw new Error("Illegal element, expected input[type='checkbox'] or input[type='radio'].");
-                       }
-                       
-                       this._options = Core.extend({
-                               hide: [],
-                               show: []
-                       }, options);
-                       
-                       ['hide', 'show'].forEach((function(type) {
-                               var element, i, length;
-                               for (i = 0, length = this._options[type].length; i < length; i++) {
-                                       element = this._options[type][i];
-                                       
-                                       if (typeof element !== 'string' && !(element instanceof Element)) {
-                                               throw new TypeError("The array '" + type + "' may only contain string selectors or DOM elements.");
-                                       }
-                               }
-                       }).bind(this));
-                       
-                       this._element.addEventListener('change', this._change.bind(this));
-                       
-                       this._handleElements(this._options.show, this._element.checked);
-                       this._handleElements(this._options.hide, !this._element.checked);
-               },
-               
-               /**
-                * Triggered when element is checked / unchecked.
-                * 
-                * @param       {Event}         event   event object
-                * @protected
-                */
-               _change: function(event) {
-                       var showElements = event.currentTarget.checked;
-                       
-                       this._handleElements(this._options.show, showElements);
-                       this._handleElements(this._options.hide, !showElements);
-               },
-               
-               /**
-                * Loops through the target elements and shows / hides them.
-                * 
-                * @param       {Array}         elements        list of elements or selectors
-                * @param       {boolean}       showElement     true if elements should be shown
-                * @protected
-                */
-               _handleElements: function(elements, showElement) {
-                       var element, tmp;
-                       for (var i = 0, length = elements.length; i < length; i++) {
-                               element = elements[i];
-                               if (typeof element === 'string') {
-                                       tmp = elBySel(element);
-                                       if (tmp === null) {
-                                               throw new Error("Unable to find element by selector '" + element + "'.");
-                                       }
-                                       
-                                       elements[i] = element = tmp;
-                               }
-                               
-                               window[(showElement ? 'elShow' : 'elHide')](element);
-                       }
-               }
-       };
-       
-       return UiToggleInput;
-});
-
-/**
- * Simple notification overlay.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/User/Editor
- */
-define('WoltLabSuite/Core/Ui/User/Editor',['Ajax', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog', 'Ui/Notification'], function(Ajax, Language, StringUtil, DomUtil, UiDialog, UiNotification) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _click: function() {},
-                       _submit: function() {},
-                       _ajaxSuccess: function() {},
-                       _ajaxSetup: function() {},
-                       _dialogSetup: function() {}
-               };
-               return Fake;
-       }
-       
-       var _actionName = '';
-       var _userHeader = null;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/User/Editor
-        */
-       return {
-               /**
-                * Initializes the user editor.
-                */
-               init: function() {
-                       _userHeader = elBySel('.userProfileUser');
-                       
-                       // init buttons
-                       ['ban', 'disableAvatar', 'disableCoverPhoto', 'disableSignature', 'enable'].forEach((function(action) {
-                               var button = elBySel('.userProfileButtonMenu .jsButtonUser' + StringUtil.ucfirst(action));
-                               
-                               // button is missing if users lacks the permission
-                               if (button) {
-                                       elData(button, 'action', action);
-                                       button.addEventListener(WCF_CLICK_EVENT, this._click.bind(this));
-                               }
-                       }).bind(this));
-               },
-               
-               /**
-                * Handles clicks on action buttons.
-                * 
-                * @param       {Event}         event           event object
-                * @protected
-                */
-               _click: function(event) {
-                       event.preventDefault();
-                       
-                       //noinspection JSCheckFunctionSignatures
-                       var action = elData(event.currentTarget, 'action');
-                       var actionName = '';
-                       switch (action) {
-                               case 'ban':
-                                       if (elDataBool(_userHeader, 'banned')) {
-                                               actionName = 'unban';
-                                       }
-                                       break;
-                               
-                               case 'disableAvatar':
-                                       if (elDataBool(_userHeader, 'disable-avatar')) {
-                                               actionName = 'enableAvatar';
-                                       }
-                                       break;
-                                       
-                               case 'disableCoverPhoto':
-                                       if (elDataBool(_userHeader, 'disable-cover-photo')) {
-                                               actionName = 'enableCoverPhoto';
-                                       }
-                                       break;
-                               
-                               case 'disableSignature':
-                                       if (elDataBool(_userHeader, 'disable-signature')) {
-                                               actionName = 'enableSignature';
-                                       }
-                                       break;
-                               
-                               case 'enable':
-                                       actionName = (elDataBool(_userHeader, 'is-disabled')) ? 'enable' : 'disable';
-                                       break;
-                       }
-                       
-                       if (actionName === '') {
-                               _actionName = action;
-                               
-                               UiDialog.open(this);
-                       }
-                       else {
-                               Ajax.api(this, {
-                                       actionName: actionName
-                               });
-                       }
-               },
-               
-               /**
-                * Handles form submit and input validation.
-                * 
-                * @param       {Event}         event           event object
-                * @protected
-                */
-               _submit: function(event) {
-                       event.preventDefault();
-                       
-                       var label = elById('wcfUiUserEditorExpiresLabel');
-                       
-                       var expires = '';
-                       var errorMessage = '';
-                       if (!elById('wcfUiUserEditorNeverExpires').checked) {
-                               expires = elById('wcfUiUserEditorExpiresDatePicker').value;
-                               if (expires === '') {
-                                       errorMessage = Language.get('wcf.global.form.error.empty');
-                               }
-                       }
-                       
-                       elInnerError(label, errorMessage);
-                       
-                       var parameters = {};
-                       parameters[_actionName + 'Expires'] = expires;
-                       parameters[_actionName + 'Reason'] = elById('wcfUiUserEditorReason').value.trim();
-                       
-                       Ajax.api(this, {
-                               actionName: _actionName,
-                               parameters: parameters
-                       });
-               },
-               
-               _ajaxSuccess: function(data) {
-                       switch (data.actionName) {
-                               case 'ban':
-                               case 'unban':
-                                       elData(_userHeader, 'banned', (data.actionName === 'ban'));
-                                       elBySel('.userProfileButtonMenu .jsButtonUserBan').textContent = Language.get('wcf.user.' + (data.actionName === 'ban' ? 'unban' : 'ban'));
-                                       
-                                       var contentTitle = elBySel('.contentTitle', _userHeader);
-                                       var banIcon = elBySel('.jsUserBanned', contentTitle);
-                                       if (data.actionName === 'ban') {
-                                               banIcon = elCreate('span');
-                                               banIcon.className = 'icon icon24 fa-lock jsUserBanned jsTooltip';
-                                               banIcon.title = data.returnValues;
-                                               contentTitle.appendChild(banIcon);
-                                       }
-                                       else if (banIcon) {
-                                               elRemove(banIcon);
-                                       }
-                                       
-                                       break;
-                               
-                               case 'disableAvatar':
-                               case 'enableAvatar':
-                                       elData(_userHeader, 'disable-avatar', (data.actionName === 'disableAvatar'));
-                                       elBySel('.userProfileButtonMenu .jsButtonUserDisableAvatar').textContent = Language.get('wcf.user.' + (data.actionName === 'disableAvatar' ? 'enable' : 'disable') + 'Avatar');
-                                       
-                                       break;
-                                       
-                               case 'disableCoverPhoto':
-                               case 'enableCoverPhoto':
-                                       elData(_userHeader, 'disable-cover-photo', (data.actionName === 'disableCoverPhoto'));
-                                       elBySel('.userProfileButtonMenu .jsButtonUserDisableCoverPhoto').textContent = Language.get('wcf.user.' + (data.actionName === 'disableCoverPhoto' ? 'enable' : 'disable') + 'CoverPhoto');
-                                       
-                                       break;
-                                       
-                               case 'disableSignature':
-                               case 'enableSignature':
-                                       elData(_userHeader, 'disable-signature', (data.actionName === 'disableSignature'));
-                                       elBySel('.userProfileButtonMenu .jsButtonUserDisableSignature').textContent = Language.get('wcf.user.' + (data.actionName === 'disableSignature' ? 'enable' : 'disable') + 'Signature');
-                                       
-                                       break;
-                               
-                               case 'enable':
-                               case 'disable':
-                                       elData(_userHeader, 'is-disabled', (data.actionName === 'disable'));
-                                       elBySel('.userProfileButtonMenu .jsButtonUserEnable').textContent = Language.get('wcf.acp.user.' + (data.actionName === 'enable' ? 'disable' : 'enable'));
-                                       
-                                       break;
-                       }
-                       
-                       if (data.actionName === 'ban' || data.actionName === 'disableAvatar' || data.actionName === 'disableCoverPhoto' || data.actionName === 'disableSignature') {
-                               UiDialog.close(this);
-                       }
-                       
-                       UiNotification.show();
-               },
-               
-               _ajaxSetup: function () {
-                       return {
-                               data: {
-                                       className: 'wcf\\data\\user\\UserAction',
-                                       objectIDs: [ elData(_userHeader, 'object-id') ]
-                               }
-                       };
-               },
-               
-               _dialogSetup: function() {
-                       return {
-                               id: 'wcfUiUserEditor',
-                               options: {
-                                       onSetup: (function (content) {
-                                               elById('wcfUiUserEditorNeverExpires').addEventListener('change', function () {
-                                                       window[(this.checked) ? 'elHide' : 'elShow'](elById('wcfUiUserEditorExpiresSettings'));
-                                               });
-                                               
-                                               elBySel('button.buttonPrimary', content).addEventListener(WCF_CLICK_EVENT, this._submit.bind(this));
-                                       }).bind(this),
-                                       onShow: function(content) {
-                                               UiDialog.setTitle('wcfUiUserEditor', Language.get('wcf.user.' + _actionName + '.confirmMessage'));
-                                               
-                                               var label = elById('wcfUiUserEditorReason').nextElementSibling;
-                                               var phrase = 'wcf.user.' + _actionName + '.reason.description';
-                                               label.textContent = Language.get(phrase);
-                                               window[(label.textContent === phrase) ? 'elHide' : 'elShow'](label);
-                                               
-                                               label = elById('wcfUiUserEditorNeverExpires').nextElementSibling;
-                                               label.textContent = Language.get('wcf.user.' + _actionName + '.neverExpires');
-                                               
-                                               label = elBySel('label[for="wcfUiUserEditorExpires"]', content);
-                                               label.textContent = Language.get('wcf.user.' + _actionName + '.expires');
-                                               
-                                               label = elById('wcfUiUserEditorExpiresLabel');
-                                               label.textContent = Language.get('wcf.user.' + _actionName + '.expires.description');
-                                       }
-                               },
-                               source: '<div class="section">'
-                                               + '<dl>'
-                                                       + '<dt><label for="wcfUiUserEditorReason">' + Language.get('wcf.global.reason') + '</label></dt>'
-                                                       + '<dd><textarea id="wcfUiUserEditorReason" cols="40" rows="3"></textarea><small></small></dd>'
-                                               + '</dl>'
-                                               + '<dl>'
-                                                       + '<dt></dt>'
-                                                       + '<dd><label><input type="checkbox" id="wcfUiUserEditorNeverExpires" checked> <span></span></label></dd>'
-                                               + '</dl>'
-                                               + '<dl id="wcfUiUserEditorExpiresSettings" style="display: none">'
-                                                       + '<dt><label for="wcfUiUserEditorExpires"></label></dt>'
-                                                       + '<dd>'
-                                                               + '<input type="date" name="wcfUiUserEditorExpires" id="wcfUiUserEditorExpires" class="medium" min="' + new Date(TIME_NOW * 1000).toISOString() + '" data-ignore-timezone="true">'
-                                                               + '<small id="wcfUiUserEditorExpiresLabel"></small>'
-                                                       + '</dd>'
-                                               +'</dl>'
-                                       + '</div>'
-                                       + '<div class="formSubmit"><button class="buttonPrimary">' + Language.get('wcf.global.button.submit') + '</button></div>'
-                       };
-               }
-       };
-});
-
-/**
- * Shows and hides an element that depends on certain selected pages when setting up conditions.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Controller/Condition/Page/Dependence
- */
-define('WoltLabSuite/Core/Controller/Condition/Page/Dependence',['Dom/ChangeListener', 'Dom/Traverse', 'EventHandler', 'ObjectMap'], function(DomChangeListener, DomTraverse, EventHandler, ObjectMap) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       register: function() {},
-                       _checkVisibility: function() {},
-                       _hideDependentElement: function() {},
-                       _showDependentElement: function() {}
-               };
-               return Fake;
-       }
-       
-       var _pages = elBySelAll('input[name="pageIDs[]"]');
-       var _dependentElements = [];
-       var _pageIds = new ObjectMap();
-       var _hiddenElements = new ObjectMap();
-       
-       var _didInit = false;
-       
-       return {
-               register: function(dependentElement, pageIds) {
-                       _dependentElements.push(dependentElement);
-                       _pageIds.set(dependentElement, pageIds);
-                       _hiddenElements.set(dependentElement, []);
-                       
-                       if (!_didInit) {
-                               for (var i = 0, length = _pages.length; i < length; i++) {
-                                       _pages[i].addEventListener('change', this._checkVisibility.bind(this));
-                               }
-                               
-                               _didInit = true;
-                       }
-                       
-                       // remove the dependent element before submit if it is hidden
-                       DomTraverse.parentByTag(dependentElement, 'FORM').addEventListener('submit', function() {
-                               if (dependentElement.style.getPropertyValue('display') === 'none') {
-                                       dependentElement.remove();
-                               }
-                       });
-                       
-                       this._checkVisibility();
-               },
-               
-               /**
-                * Checks if only relevant pages are selected. If that is the case, the dependent
-                * element is shown, otherwise it is hidden.
-                * 
-                * @private
-                */
-               _checkVisibility: function() {
-                       var dependentElement, page, pageIds, checkedPageIds, irrelevantPageIds;
-                       
-                       depenentElementLoop: for (var i = 0, length = _dependentElements.length; i < length; i++) {
-                               dependentElement = _dependentElements[i];
-                               pageIds = _pageIds.get(dependentElement);
-                               
-                               checkedPageIds = [];
-                               for (var j = 0, length2 = _pages.length; j < length2; j++) {
-                                       page = _pages[j];
-                                       
-                                       if (page.checked) {
-                                               checkedPageIds.push(~~page.value);
-                                       }
-                               }
-                               
-                               irrelevantPageIds = checkedPageIds.filter(function(pageId) {
-                                       return pageIds.indexOf(pageId) === -1;
-                               });
-                               
-                               if (!checkedPageIds.length || irrelevantPageIds.length) {
-                                       this._hideDependentElement(dependentElement);
-                               }
-                               else {
-                                       this._showDependentElement(dependentElement);
-                               }
-                       }
-                       
-                       EventHandler.fire('com.woltlab.wcf.pageConditionDependence', 'checkVisivility');
-               },
-               
-               /**
-                * Hides all elements that depend on the given element.
-                * 
-                * @param       {HTMLElement}   dependentElement
-                */
-               _hideDependentElement: function(dependentElement) {
-                       elHide(dependentElement);
-                       
-                       var hiddenElements = _hiddenElements.get(dependentElement);
-                       for (var i = 0, length = hiddenElements.length; i < length; i++) {
-                               elHide(hiddenElements[i]);
-                       }
-                       
-                       _hiddenElements.set(dependentElement, []);
-               },
-               
-               /**
-                * Shows all elements that depend on the given element.
-                * 
-                * @param       {HTMLElement}   dependentElement
-                */
-               _showDependentElement: function(dependentElement) {
-                       elShow(dependentElement);
-                       
-                       // make sure that all parent elements are also visible
-                       var parentNode = dependentElement;
-                       while ((parentNode = parentNode.parentNode) && parentNode instanceof Element) {
-                               if (parentNode.style.getPropertyValue('display') === 'none') {
-                                       _hiddenElements.get(dependentElement).push(parentNode);
-                               }
-                               
-                               elShow(parentNode);
-                       }
-               }
-       };
-});
-
-/**
- * Map route planner based on Google Maps.
- *
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Controller/Map/Route/Planner
- */
-define('WoltLabSuite/Core/Controller/Map/Route/Planner',[
-       'Dom/Traverse',
-       'Dom/Util',
-       'Language',
-       'Ui/Dialog',
-       'WoltLabSuite/Core/Ajax/Status'
-], function(
-       DomTraverse,
-       DomUtil,
-       Language,
-       UiDialog,
-       AjaxStatus
-) {
-       /**
-        * @constructor
-        */
-       function Planner(buttonId, destination) {
-               this._button = elById(buttonId);
-               if (this._button === null) {
-                       throw new Error("Unknown button with id '" + buttonId + "'");
-               }
-               
-               this._button.addEventListener('click', this._openDialog.bind(this));
-               
-               this._destination = destination;
-       }
-       Planner.prototype = {
-               /**
-                * Sets up the route planner dialog.
-                */
-               _dialogSetup: function() {
-                       return {
-                               id: this._button.id + 'Dialog',
-                               options: {
-                                       onShow: this._initDialog.bind(this),
-                                       title: Language.get('wcf.map.route.planner')
-                               },
-                               source: '<div class="googleMapsDirectionsContainer" style="display: none;">' +
-                                               '<div class="googleMap"></div>' +
-                                               '<div class="googleMapsDirections"></div>' +
-                                       '</div>' +
-                                       '<small class="googleMapsDirectionsGoogleLinkContainer"><a href="' + this._getGoogleMapsLink() + '" class="googleMapsDirectionsGoogleLink" target="_blank" style="display: none;">' + Language.get('wcf.map.route.viewOnGoogleMaps') + '</a></small>' +
-                                       '<dl>' +
-                                               '<dt>' + Language.get('wcf.map.route.origin') + '</dt>' +
-                                               '<dd><input type="text" name="origin" class="long" autofocus /></dd>' +
-                                       '</dl>' +
-                                       '<dl style="display: none;">' +
-                                               '<dt>' + Language.get('wcf.map.route.travelMode') + '</dt>' +
-                                               '<dd>' +
-                                                       '<select name="travelMode">' +
-                                                               '<option value="driving">' + Language.get('wcf.map.route.travelMode.driving') + '</option>' + 
-                                                               '<option value="walking">' + Language.get('wcf.map.route.travelMode.walking') + '</option>' + 
-                                                               '<option value="bicycling">' + Language.get('wcf.map.route.travelMode.bicycling') + '</option>' +
-                                                               '<option value="transit">' + Language.get('wcf.map.route.travelMode.transit') + '</option>' +
-                                                       '</select>' +
-                                               '</dd>' +
-                                       '</dl>'
-                       }
-               },
-               
-               /**
-                * Calculates the route based on the given result of a location search.
-                * 
-                * @param       {object}        data
-                */
-               _calculateRoute: function(data) {
-                       var dialog = UiDialog.getDialog(this).dialog;
-                       
-                       if (data.label) {
-                               this._originInput.value = data.label;
-                       }
-                       
-                       if (this._map === undefined) {
-                               this._map = new google.maps.Map(elByClass('googleMap', dialog)[0], {
-                                       disableDoubleClickZoom: WCF.Location.GoogleMaps.Settings.get('disableDoubleClickZoom'),
-                                       draggable: WCF.Location.GoogleMaps.Settings.get('draggable'),
-                                       mapTypeId: google.maps.MapTypeId.ROADMAP,
-                                       scaleControl: WCF.Location.GoogleMaps.Settings.get('scaleControl'),
-                                       scrollwheel: WCF.Location.GoogleMaps.Settings.get('scrollwheel')
-                               });
-                               
-                               this._directionsService = new google.maps.DirectionsService();
-                               this._directionsRenderer = new google.maps.DirectionsRenderer();
-                               
-                               this._directionsRenderer.setMap(this._map);
-                               this._directionsRenderer.setPanel(elByClass('googleMapsDirections', dialog)[0]);
-                               
-                               this._googleLink = elByClass('googleMapsDirectionsGoogleLink', dialog)[0];
-                       }
-                       
-                       var request = {
-                               destination: this._destination,
-                               origin: data.location,
-                               provideRouteAlternatives: true,
-                               travelMode: google.maps.TravelMode[this._travelMode.value.toUpperCase()]
-                       };
-                       
-                       AjaxStatus.show();
-                       this._directionsService.route(request, this._setRoute.bind(this));
-                       
-                       elAttr(this._googleLink, 'href', this._getGoogleMapsLink(data.location, this._travelMode.value));
-                       
-                       this._lastOrigin = data.location;
-               },
-               
-               /**
-                * Returns the Google Maps link based on the given optional directions origin
-                * and optional travel mode.
-                * 
-                * @param       {google.maps.LatLng}    origin
-                * @param       {string}                travelMode
-                * @return      {string}
-                */
-               _getGoogleMapsLink: function(origin, travelMode) {
-                       if (origin) {
-                               var link = 'https://www.google.com/maps/dir/?api=1' +
-                                               '&origin=' + origin.lat() + ',' + origin.lng() + '' +
-                                               '&destination=' + this._destination.lat() + ',' + this._destination.lng();
-                               
-                               if (travelMode) {
-                                       link += '&travelmode=' + travelMode;
-                               }
-                               
-                               return link;
-                       }
-                       
-                       return 'https://www.google.com/maps/search/?api=1&query=' + this._destination.lat() + ',' + this._destination.lng();
-               },
-               
-               /**
-                * Initializes the route planning dialog.
-                */
-               _initDialog: function() {
-                       if (!this._didInitDialog) {
-                               var dialog = UiDialog.getDialog(this).dialog;
-                               
-                               // make input element a location search
-                               this._originInput = elBySel('input[name="origin"]', dialog);
-                               new WCF.Location.GoogleMaps.LocationSearch(this._originInput, this._calculateRoute.bind(this));
-                               
-                               this._travelMode = elBySel('select[name="travelMode"]', dialog);
-                               this._travelMode.addEventListener('change', this._updateRoute.bind(this));
-                               
-                               this._didInitDialog = true;
-                       }
-               },
-               
-               /**
-                * Opens the route planning dialog.
-                */
-               _openDialog: function() {
-                       UiDialog.open(this);
-               },
-               
-               /**
-                * Handles the response of the direction service.
-                * 
-                * @param       {object}        result
-                * @param       {string}        status
-                */
-               _setRoute: function(result, status) {
-                       AjaxStatus.hide();
-                       
-                       if (status === 'OK') {
-                               elShow(this._map.getDiv().parentNode);
-                               
-                               google.maps.event.trigger(this._map, 'resize');
-                               
-                               this._directionsRenderer.setDirections(result);
-                               
-                               elShow(DomTraverse.parentByTag(this._travelMode, 'DL'));
-                               elShow(this._googleLink);
-                               
-                               elInnerError(this._originInput, false);
-                       }
-                       else {
-                               // map irrelevant errors to not found error
-                               if (status !== 'OVER_QUERY_LIMIT' && status !== 'REQUEST_DENIED') {
-                                       status = 'NOT_FOUND';
-                               }
-                               
-                               elInnerError(this._originInput, Language.get('wcf.map.route.error.' + status.toLowerCase()));
-                       }
-               },
-               
-               /**
-                * Updates the route after the travel mode has been changed.
-                */
-               _updateRoute: function() {
-                       this._calculateRoute({
-                               location: this._lastOrigin
-                       });
-               }
-       };
-       
-       return Planner;
-});
-
-/**
- * Handles email notification type for user notification settings.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Controller/User/Notification/Settings
- */
-define('WoltLabSuite/Core/Controller/User/Notification/Settings',['Dictionary', 'Language', 'Dom/Traverse', 'Ui/SimpleDropdown'], function(Dictionary, Language, DomTraverse, UiSimpleDropdown) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       setup: function() {},
-                       _initGroup: function() {},
-                       _click: function() {},
-                       _createDropdown: function() {},
-                       _selectType: function() {}
-               };
-               return Fake;
-       }
-       
-       var _data = new Dictionary();
-       
-       var _callbackClick = null;
-       var _callbackSelectType = null;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Controller/User/Notification/Settings
-        */
-       var ControllerUserNotificationSettings = {
-               /**
-                * Binds event listeners for all notifications supporting emails.
-                */
-               setup: function() {
-                       _callbackClick = this._click.bind(this);
-                       _callbackSelectType = this._selectType.bind(this);
-                       
-                       var group, mailSetting, groups = elBySelAll('#notificationSettings .flexibleButtonGroup');
-                       for (var i = 0, length = groups.length; i < length; i++) {
-                               group = groups[i];
-                               
-                               mailSetting = elBySel('.notificationSettingsEmail', group);
-                               if (mailSetting === null) {
-                                       continue;
-                               }
-                               
-                               this._initGroup(group, mailSetting);
-                       }
-               },
-               
-               /**
-                * Initializes a setting.
-                * 
-                * @param       {Element}       group           button group element
-                * @param       {Element}       mailSetting     mail settings element
-                */
-               _initGroup: function(group, mailSetting) {
-                       var groupId = ~~elData(group, 'object-id');
-                       
-                       var disabledNotification = elById('settings_' + groupId + '_disabled');
-                       disabledNotification.addEventListener(WCF_CLICK_EVENT, function() { mailSetting.classList.remove('active'); });
-                       var enabledNotification = elById('settings_' + groupId + '_enabled');
-                       enabledNotification.addEventListener(WCF_CLICK_EVENT, function() { mailSetting.classList.add('active'); });
-                       
-                       var mailValue = DomTraverse.childByTag(mailSetting, 'INPUT');
-                       
-                       var button = DomTraverse.childByTag(mailSetting, 'A');
-                       elData(button, 'object-id', groupId);
-                       button.addEventListener(WCF_CLICK_EVENT, _callbackClick);
-                       
-                       _data.set(groupId, {
-                               button: button,
-                               dropdownMenu: null,
-                               mailSetting: mailSetting,
-                               mailValue: mailValue
-                       });
-               },
-               
-               /**
-                * Creates and displays the email type dropdown.
-                * 
-                * @param       {Object}        event           event object
-                */
-               _click: function(event) {
-                       event.preventDefault();
-                       
-                       var button = event.currentTarget;
-                       var objectId = ~~elData(button, 'object-id');
-                       var data = _data.get(objectId);
-                       if (data.dropdownMenu === null) {
-                               data.dropdownMenu = this._createDropdown(objectId, data.mailValue.value);
-                               
-                               button.parentNode.classList.add('dropdown');
-                               button.parentNode.appendChild(data.dropdownMenu);
-                               
-                               UiSimpleDropdown.init(button, event);
-                       }
-                       else {
-                               var items = DomTraverse.childrenByTag(data.dropdownMenu, 'LI'), value = data.mailValue.value;
-                               for (var i = 0; i < 4; i++) {
-                                       items[i].classList[(elData(items[i], 'value') === value) ? 'add' : 'remove']('active');
-                               }
-                       }
-               },
-               
-               /**
-                * Creates the email type dropdown.
-                * 
-                * @param       {int}           objectId        notification event id
-                * @param       {string}        initialValue    initial email type
-                * @returns     {Element}       dropdown menu object
-                */
-               _createDropdown: function(objectId, initialValue) {
-                       var dropdownMenu = elCreate('ul');
-                       dropdownMenu.className = 'dropdownMenu';
-                       elData(dropdownMenu, 'object-id', objectId);
-                       
-                       var link, listItem, value, items = ['instant', 'daily', 'divider', 'none'];
-                       for (var i = 0; i < 4; i++) {
-                               value = items[i];
-                               
-                               listItem = elCreate('li');
-                               if (value === 'divider') {
-                                       listItem.className = 'dropdownDivider';
-                               }
-                               else {
-                                       link = elCreate('a');
-                                       link.textContent = Language.get('wcf.user.notification.mailNotificationType.' + value);
-                                       listItem.appendChild(link);
-                                       elData(listItem, 'value', value);
-                                       listItem.addEventListener(WCF_CLICK_EVENT, _callbackSelectType);
-                                       
-                                       if (initialValue === value) {
-                                               listItem.className = 'active';
-                                       }
-                               }
-                               
-                               dropdownMenu.appendChild(listItem);
-                       }
-                       
-                       return dropdownMenu;
-               },
-               
-               /**
-                * Sets the selected email notification type.
-                * 
-                * @param       {Object}        event           event object
-                */
-               _selectType: function(event) {
-                       var value = elData(event.currentTarget, 'value');
-                       var groupId = ~~elData(event.currentTarget.parentNode, 'object-id');
-                       
-                       var data = _data.get(groupId);
-                       data.mailValue.value = value;
-                       elBySel('span.title', data.mailSetting).textContent = Language.get('wcf.user.notification.mailNotificationType.' + value);
-                       
-                       data.button.classList[(value === 'none') ? 'remove' : 'add']('yellow');
-                       data.button.classList[(value === 'none') ? 'remove' : 'add']('active');
-               }
-       };
-       
-       return ControllerUserNotificationSettings;
-});
-
-/**
- * Data handler for a captcha form builder field in an Ajax form.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Captcha
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Captcha',['Core', './Field', 'WoltLabSuite/Core/Controller/Captcha'], function(Core, FormBuilderField, Captcha) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function FormBuilderFieldCaptcha(fieldId) {
-               this.init(fieldId);
-       };
-       Core.inherit(FormBuilderFieldCaptcha, FormBuilderField, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#getData
-                */
-               _getData: function() {
-                       if (Captcha.has(this._fieldId)) {
-                               return Captcha.getData(this._fieldId);
-                       }
-                       
-                       return {};
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_readField
-                */
-               _readField: function() {
-                       // does nothing
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#destroy
-                */
-               destroy: function() {
-                       if (Captcha.has(this._fieldId)) {
-                               Captcha.delete(this._fieldId);
-                       }
-               }
-       });
-       
-       return FormBuilderFieldCaptcha;
-});
-
-/**
- * Data handler for a form builder field in an Ajax form represented by checkboxes.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Checkboxes
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Checkboxes',['Core', './Field'], function(Core, FormBuilderField) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function FormBuilderFieldCheckboxes(fieldId) {
-               this.init(fieldId);
-       };
-       Core.inherit(FormBuilderFieldCheckboxes, FormBuilderField, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_getData
-                */
-               _getData: function() {
-                       var data = {};
-                       
-                       data[this._fieldId] = [];
-                       
-                       for (var i = 0, length = this._fields.length; i < length; i++) {
-                               if (this._fields[i].checked) {
-                                       data[this._fieldId].push(this._fields[i].value);
-                               }
-                       }
-                       
-                       return data;
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_readField
-                */
-               _readField: function() {
-                       this._fields = elBySelAll('input[name="' + this._fieldId + '[]"]');
-               }
-       });
-       
-       return FormBuilderFieldCheckboxes;
-});
-
-/**
- * Data handler for a form builder field in an Ajax form that stores its value via a checkbox being
- * checked or not.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Checked
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Checked',['Core', './Field'], function(Core, FormBuilderField) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function FormBuilderFieldInput(fieldId) {
-               this.init(fieldId);
-       };
-       Core.inherit(FormBuilderFieldInput, FormBuilderField, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_getData
-                */
-               _getData: function() {
-                       var data = {};
-                       
-                       data[this._fieldId] = ~~this._field.checked;
-                       
-                       return data;
-               }
-       });
-       
-       return FormBuilderFieldInput;
-});
-
-/**
- * Data handler for a date form builder field in an Ajax form.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Date
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Date',['Core', 'WoltLabSuite/Core/Date/Picker', './Field'], function(Core, DatePicker, FormBuilderField) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function FormBuilderFieldDate(fieldId) {
-               this.init(fieldId);
-       };
-       Core.inherit(FormBuilderFieldDate, FormBuilderField, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_getData
-                */
-               _getData: function() {
-                       var data = {};
-                       
-                       data[this._fieldId] = DatePicker.getValue(this._field);
-                       
-                       return data;
-               }
-       });
-       
-       return FormBuilderFieldDate;
-});
-
-/**
- * Data handler for an item list form builder field in an Ajax form.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/ItemList
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/ItemList',['Core', './Field', 'WoltLabSuite/Core/Ui/ItemList/Static'], function(Core, FormBuilderField, UiItemListStatic) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function FormBuilderFieldItemList(fieldId) {
-               this.init(fieldId);
-       };
-       Core.inherit(FormBuilderFieldItemList, FormBuilderField, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_getData
-                */
-               _getData: function() {
-                       var data = {};
-                       data[this._fieldId] = [];
-                       
-                       var values = UiItemListStatic.getValues(this._fieldId);
-                       for (var i = 0, length = values.length; i < length; i++) {
-                               // TODO: data[this._fieldId] is an array but if code assumes object
-                               if (values[i].objectId) {
-                                       data[this._fieldId][values[i].objectId] = values[i].value;
-                               }
-                               else {
-                                       data[this._fieldId].push(values[i].value);
-                               }
-                       }
-                       
-                       return data;
-               }
-       });
-       
-       return FormBuilderFieldItemList;
-});
-
-/**
- * Data handler for a radio button form builder field in an Ajax form.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/RadioButton
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/RadioButton',['Core', './Field'], function(Core, FormBuilderField) {
-       "use strict";
-
-       /**
-        * @constructor
-        */
-       function FormBuilderFieldRadioButton(fieldId) {
-               this.init(fieldId);
-       };
-       Core.inherit(FormBuilderFieldRadioButton, FormBuilderField, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#getData
-                */
-               _getData: function() {
-                       var data = {};
-                       
-                       for (var i = 0, length = this._fields.length; i < length; i++) {
-                               if (this._fields[i].checked) {
-                                       data[this._fieldId] = this._fields[i].value;
-                                       break;
-                               }
-                       }
-                       
-                       return data;
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_readField
-                */
-               _readField: function() {
-                       this._fields = elBySelAll('input[name=' + this._fieldId + ']');
-               },
-       });
-       
-       return FormBuilderFieldRadioButton;
-});
-
-/**
- * Data handler for a simple acl form builder field in an Ajax form.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/SimpleAcl
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/SimpleAcl',['Core', './Field'], function(Core, FormBuilderField) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function FormBuilderFieldSimpleAcl(fieldId) {
-               this.init(fieldId);
-       };
-       Core.inherit(FormBuilderFieldSimpleAcl, FormBuilderField, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_getData
-                */
-               _getData: function() {
-                       var groupIds = [];
-                       elBySelAll('input[name="' + this._fieldId + '[group][]"]', undefined, function(input) {
-                               groupIds.push(~~input.value);
-                       });
-                       
-                       var usersIds = [];
-                       elBySelAll('input[name="' + this._fieldId + '[user][]"]', undefined, function(input) {
-                               usersIds.push(~~input.value);
-                       });
-                       
-                       var data = {};
-                       
-                       data[this._fieldId] = {
-                               group: groupIds,
-                               user: usersIds
-                       };
-                       
-                       return data;
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_readField
-                */
-               _readField: function() {
-                       // does nothing
-               }
-       });
-       
-       return FormBuilderFieldSimpleAcl;
-});
-
-/**
- * Data handler for a tag form builder field in an Ajax form.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Tag
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Tag',['Core', './Field', 'WoltLabSuite/Core/Ui/ItemList'], function(Core, FormBuilderField, UiItemList) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function FormBuilderFieldTag(fieldId) {
-               this.init(fieldId);
-       };
-       Core.inherit(FormBuilderFieldTag, FormBuilderField, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_getData
-                */
-               _getData: function() {
-                       var data = {};
-                       data[this._fieldId] = [];
-                       
-                       var values = UiItemList.getValues(this._fieldId);
-                       for (var i = 0, length = values.length; i < length; i++) {
-                               data[this._fieldId].push(values[i].value);
-                       }
-                       
-                       return data;
-               }
-       });
-       
-       return FormBuilderFieldTag;
-});
-
-/**
- * Data handler for a user form builder field in an Ajax form.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/User
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/User',['Core', './Field', 'WoltLabSuite/Core/Ui/ItemList'], function(Core, FormBuilderField, UiItemList) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function FormBuilderFieldUser(fieldId) {
-               this.init(fieldId);
-       };
-       Core.inherit(FormBuilderFieldUser, FormBuilderField, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_getData
-                */
-               _getData: function() {
-                       var values = UiItemList.getValues(this._fieldId);
-                       var usernames = [];
-                       for (var i = 0, length = values.length; i < length; i++) {
-                               if (values[i].objectId) {
-                                       usernames.push(values[i].value);
-                               }
-                       }
-                       
-                       var data = {};
-                       data[this._fieldId] = usernames.join(',');
-                       
-                       return data;
-               }
-       });
-       
-       return FormBuilderFieldUser;
-});
-
-/**
- * Data handler for a form builder field in an Ajax form that stores its value in an input's value
- * attribute.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Value
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Value',['Core', './Field'], function(Core, FormBuilderField) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function FormBuilderFieldValue(fieldId) {
-               this.init(fieldId);
-       };
-       Core.inherit(FormBuilderFieldValue, FormBuilderField, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_getData
-                */
-               _getData: function() {
-                       var data = {};
-                       
-                       data[this._fieldId] = this._field.value;
-                       
-                       return data;
-               }
-       });
-       
-       return FormBuilderFieldValue;
-});
-
-/**
- * Data handler for an i18n form builder field in an Ajax form that stores its value in an input's
- * value attribute.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/ValueI18n
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/ValueI18n',['Core', './Field', 'WoltLabSuite/Core/Language/Input'], function(Core, FormBuilderField, LanguageInput) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function FormBuilderFieldValueI18n(fieldId) {
-               this.init(fieldId);
-       };
-       Core.inherit(FormBuilderFieldValueI18n, FormBuilderField, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_getData
-                */
-               _getData: function() {
-                       var data = {};
-                       
-                       var values = LanguageInput.getValues(this._fieldId);
-                       if (values.size > 1) {
-                               data[this._fieldId + '_i18n'] = values.toObject();
-                       }
-                       else {
-                               data[this._fieldId] = values.get(0);
-                       }
-                       
-                       return data;
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#destroy
-                */
-               destroy: function() {
-                       LanguageInput.unregister(this._fieldId);
-               }
-       });
-       
-       return FormBuilderFieldValueI18n;
-});
-
-/**
- * Handles the comment response add feature.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Comment/Add
- */
-define('WoltLabSuite/Core/Ui/Comment/Response/Add',[
-       'Core', 'Language', 'Dom/ChangeListener', 'Dom/Util', 'Dom/Traverse', 'Ui/Notification',  'WoltLabSuite/Core/Ui/Comment/Add'
-],
-function(
-       Core, Language, DomChangeListener, DomUtil, DomTraverse, UiNotification, UiCommentAdd
-) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       getContainer: function() {},
-                       getContent: function() {},
-                       setContent: function() {},
-                       _submitGuestDialog: function() {},
-                       _submit: function() {},
-                       _getParameters: function () {},
-                       _validate: function() {},
-                       throwError: function() {},
-                       _showLoadingOverlay: function() {},
-                       _hideLoadingOverlay: function() {},
-                       _reset: function() {},
-                       _handleError: function() {},
-                       _getEditor: function() {},
-                       _insertMessage: function() {},
-                       _ajaxSuccess: function() {},
-                       _ajaxFailure: function() {},
-                       _ajaxSetup: function() {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * @constructor
-        */
-       function UiCommentResponseAdd(container, options) { this.init(container, options); }
-       Core.inherit(UiCommentResponseAdd, UiCommentAdd, {
-               init: function (container, options) {
-                       UiCommentResponseAdd._super.prototype.init.call(this, container);
-                       
-                       this._options = Core.extend({
-                               callbackInsert: null
-                       }, options);
-               },
-               
-               /**
-                * Returns the editor container for placement or `null` if the editor is busy.
-                * 
-                * @return      {(Element|null)}
-                */
-               getContainer: function() {
-                       return (this._isBusy) ? null : this._container;
-               },
-               
-               /**
-                * Retrieves the current content from the editor.
-                * 
-                * @return      {string}
-                */
-               getContent: function () {
-                       return window.jQuery(this._textarea).redactor('code.get');
-               },
-               
-               /**
-                * Sets the content and places the caret at the end of the editor.
-                * 
-                * @param       {string}        html
-                */
-               setContent: function (html) {
-                       window.jQuery(this._textarea).redactor('code.set', html);
-                       window.jQuery(this._textarea).redactor('WoltLabCaret.endOfEditor');
-                       
-                       // the error message can appear anywhere in the container, not exclusively after the textarea
-                       var innerError = elBySel('.innerError', this._textarea.parentNode);
-                       if (innerError !== null) elRemove(innerError);
-                       
-                       this._content.classList.remove('collapsed');
-                       this._focusEditor();
-               },
-               
-               _getParameters: function () {
-                       var parameters = UiCommentResponseAdd._super.prototype._getParameters.call(this);
-                       parameters.data.commentID = ~~elData(this._container.closest('.comment'), 'object-id');
-                       
-                       return parameters;
-               },
-               
-               _insertMessage: function(data) {
-                       var commentContent = DomTraverse.childByClass(this._container.parentNode, 'commentContent');
-                       var responseList = commentContent.nextElementSibling;
-                       if (responseList === null || !responseList.classList.contains('commentResponseList')) {
-                               responseList = elCreate('ul');
-                               responseList.className = 'containerList commentResponseList';
-                               elData(responseList, 'responses', 0);
-                               
-                               commentContent.parentNode.insertBefore(responseList, commentContent.nextSibling);
-                       }
-                       
-                       // insert HTML
-                       //noinspection JSCheckFunctionSignatures
-                       DomUtil.insertHtml(data.returnValues.template, responseList, 'append');
-                       
-                       UiNotification.show(Language.get('wcf.global.success.add'));
-                       
-                       DomChangeListener.trigger();
-                       
-                       // reset editor
-                       window.jQuery(this._textarea).redactor('code.set', '');
-                       
-                       if (this._options.callbackInsert !== null) this._options.callbackInsert();
-                       
-                       // update counter
-                       elData(responseList, 'responses', responseList.children.length);
-                       
-                       return responseList.lastElementChild;
-               },
-               
-               _ajaxSetup: function() {
-                       var data = UiCommentResponseAdd._super.prototype._ajaxSetup.call(this);
-                       data.data.actionName = 'addResponse';
-                       
-                       return data;
-               }
-       });
-       
-       return UiCommentResponseAdd;
-});
-
-/**
- * Provides editing support for comment responses.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Comment/Response/Edit
- */
-define(
-       'WoltLabSuite/Core/Ui/Comment/Response/Edit',[
-               'Ajax',         'Core',            'Dictionary',          'Environment',
-               'EventHandler', 'Language',        'List',                'Dom/ChangeListener', 'Dom/Traverse',
-               'Dom/Util',     'Ui/Notification', 'Ui/ReusableDropdown', 'WoltLabSuite/Core/Ui/Scroll', 'WoltLabSuite/Core/Ui/Comment/Edit'
-       ],
-       function(
-               Ajax,            Core,              Dictionary,            Environment,
-               EventHandler,    Language,          List,                  DomChangeListener,    DomTraverse,
-               DomUtil,         UiNotification,    UiReusableDropdown,    UiScroll, UiCommentEdit
-       )
-{
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       rebuild: function() {},
-                       _click: function() {},
-                       _prepare: function() {},
-                       _showEditor: function() {},
-                       _restoreMessage: function() {},
-                       _save: function() {},
-                       _validate: function() {},
-                       throwError: function() {},
-                       _showMessage: function() {},
-                       _hideEditor: function() {},
-                       _restoreEditor: function() {},
-                       _destroyEditor: function() {},
-                       _getEditorId: function() {},
-                       _getObjectId: function() {},
-                       _ajaxFailure: function() {},
-                       _ajaxSuccess: function() {},
-                       _ajaxSetup: function() {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * @constructor
-        */
-       function UiCommentResponseEdit(container) { this.init(container); }
-       Core.inherit(UiCommentResponseEdit, UiCommentEdit, {
-               /**
-                * Initializes the comment edit manager.
-                * 
-                * @param       {Element}       container       container element
-                */
-               init: function(container) {
-                       this._activeElement = null;
-                       this._callbackClick = null;
-                       this._container = container;
-                       this._editorContainer = null;
-                       this._responses = new List();
-                       
-                       this.rebuild();
-                       
-                       DomChangeListener.add('Ui/Comment/Response/Edit_' + DomUtil.identify(this._container), this.rebuild.bind(this));
-               },
-               
-               /**
-                * Initializes each applicable message, should be called whenever new
-                * messages are being displayed.
-                */
-               rebuild: function() {
-                       elBySelAll('.commentResponse', this._container, (function (response) {
-                               if (this._responses.has(response)) {
-                                       return;
-                               }
-                               
-                               if (elDataBool(response, 'can-edit')) {
-                                       var button = elBySel('.jsCommentResponseEditButton', response);
-                                       if (button !== null) {
-                                               if (this._callbackClick === null) {
-                                                       this._callbackClick = this._click.bind(this);
-                                               }
-                                               
-                                               button.addEventListener(WCF_CLICK_EVENT, this._callbackClick);
-                                       }
-                               }
-                               
-                               this._responses.add(response);
-                       }).bind(this));
-               },
-               
-               /**
-                * Handles clicks on the edit button.
-                *
-                * @param       {?Event}        event           event object
-                * @protected
-                */
-               _click: function(event) {
-                       event.preventDefault();
-                       
-                       if (this._activeElement === null) {
-                               this._activeElement = event.currentTarget.closest('.commentResponse');
-                               
-                               this._prepare();
-                               
-                               Ajax.api(this, {
-                                       actionName: 'beginEdit',
-                                       objectIDs: [this._getObjectId(this._activeElement)]
-                               });
-                       }
-                       else {
-                               UiNotification.show('wcf.message.error.editorAlreadyInUse', null, 'warning');
-                       }
-               },
-               
-               /**
-                * Prepares the message for editor display.
-                * 
-                * @protected
-                */
-               _prepare: function() {
-                       this._editorContainer = elCreate('div');
-                       this._editorContainer.className = 'commentEditorContainer';
-                       this._editorContainer.innerHTML = '<span class="icon icon48 fa-spinner"></span>';
-                       
-                       var content = elBySel('.commentResponseContent', this._activeElement);
-                       content.insertBefore(this._editorContainer, content.firstChild);
-               },
-               
-               /**
-                * Shows the update message.
-                * 
-                * @param       {Object}        data            ajax response data
-                * @protected
-                */
-               _showMessage: function(data) {
-                       // set new content
-                       //noinspection JSCheckFunctionSignatures
-                       DomUtil.setInnerHtml(elBySel('.commentResponseContent .userMessage', this._editorContainer.parentNode), data.returnValues.message);
-                       
-                       this._restoreMessage();
-                       
-                       UiNotification.show();
-               },
-               
-               /**
-                * Returns the unique editor id.
-                * 
-                * @return      {string}        editor id
-                * @protected
-                */
-               _getEditorId: function() {
-                       return 'commentResponseEditor' + this._getObjectId(this._activeElement);
-               },
-               
-               _ajaxSetup: function() {
-                       var objectTypeId = ~~elData(this._container, 'object-type-id');
-                       
-                       return {
-                               data: {
-                                       className: 'wcf\\data\\comment\\response\\CommentResponseAction',
-                                       parameters: {
-                                               data: {
-                                                       objectTypeID: objectTypeId
-                                               }
-                                       }
-                               },
-                               silent: true
-                       };
-               }
-       });
-       
-       return UiCommentResponseEdit;
-});
-
-/**
- * Manages the sticky page header.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Page/Header/Fixed
- */
-define('WoltLabSuite/Core/Ui/Page/Header/Fixed',['Core', 'EventHandler', 'Ui/Alignment', 'Ui/CloseOverlay', 'Ui/SimpleDropdown', 'Ui/Screen'], function(Core, EventHandler, UiAlignment, UiCloseOverlay, UiSimpleDropdown, UiScreen) {
-       "use strict";
-       
-       var _pageHeader, _pageHeaderContainer, _pageHeaderPanel, _pageHeaderSearch, _searchInput, _topMenu, _userPanelSearchButton;
-       var _isMobile = false;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Page/Header/Fixed
-        */
-       return {
-               /**
-                * Initializes the sticky page header handler.
-                */
-               init: function() {
-                       _pageHeader = elById('pageHeader');
-                       _pageHeaderContainer = elById('pageHeaderContainer');
-                       
-                       this._initSearchBar();
-                       
-                       UiScreen.on('screen-md-down', {
-                               match: function () { _isMobile = true; },
-                               unmatch: function () { _isMobile = false; },
-                               setup: function () { _isMobile = true; }
-                       });
-                       
-                       EventHandler.add('com.woltlab.wcf.Search', 'close', this._closeSearchBar.bind(this));
-               },
-               
-               /**
-                * Provides the collapsible search bar.
-                * 
-                * @protected
-                */
-               _initSearchBar: function() {
-                       _pageHeaderSearch = elById('pageHeaderSearch');
-                       _pageHeaderSearch.addEventListener(WCF_CLICK_EVENT, function(event) { event.stopPropagation(); });
-                       
-                       _pageHeaderPanel = elById('pageHeaderPanel');
-                       _searchInput = elById('pageHeaderSearchInput');
-                       _topMenu = elById('topMenu');
-                       
-                       _userPanelSearchButton = elById('userPanelSearchButton');
-                       _userPanelSearchButton.addEventListener(WCF_CLICK_EVENT, (function(event) {
-                               event.preventDefault();
-                               event.stopPropagation();
-                               
-                               if (_pageHeader.classList.contains('searchBarOpen')) {
-                                       this._closeSearchBar();
-                               }
-                               else {
-                                       this._openSearchBar();
-                               }
-                       }).bind(this));
-                       
-                       UiCloseOverlay.add('WoltLabSuite/Core/Ui/Page/Header/Fixed', (function() {
-                               if (_pageHeader.classList.contains('searchBarForceOpen')) return;
-                               
-                               this._closeSearchBar();
-                       }).bind(this));
-                       
-                       EventHandler.add('com.woltlab.wcf.MainMenuMobile', 'more', (function(data) {
-                               if (data.identifier === 'com.woltlab.wcf.search') {
-                                       data.handler.close(true);
-                                       
-                                       Core.triggerEvent(_userPanelSearchButton, WCF_CLICK_EVENT);
-                               }
-                       }).bind(this));
-               },
-               
-               /**
-                * Opens the search bar.
-                * 
-                * @protected
-                */
-               _openSearchBar: function() {
-                       window.WCF.Dropdown.Interactive.Handler.closeAll();
-                       
-                       _pageHeader.classList.add('searchBarOpen');
-                       _userPanelSearchButton.parentNode.classList.add('open');
-                       
-                       if (!_isMobile) {
-                               // calculate value for `right` on desktop
-                               UiAlignment.set(_pageHeaderSearch, _topMenu, {
-                                       horizontal: 'right'
-                               });
-                       }
-                       
-                       _pageHeaderSearch.style.setProperty('top', _pageHeaderPanel.clientHeight + 'px', '');
-                       _searchInput.focus();
-                       window.setTimeout(function() {
-                               _searchInput.selectionStart = _searchInput.selectionEnd = _searchInput.value.length;
-                       }, 1);
-               },
-               
-               /**
-                * Closes the search bar.
-                * 
-                * @protected
-                */
-               _closeSearchBar: function () {
-                       _pageHeader.classList.remove('searchBarOpen');
-                       _userPanelSearchButton.parentNode.classList.remove('open');
-                       
-                       ['bottom', 'left', 'right', 'top'].forEach(function(propertyName) {
-                               _pageHeaderSearch.style.removeProperty(propertyName);
-                       });
-                       
-                       _searchInput.blur();
-                       
-                       // close the scope selection
-                       var scope = elBySel('.pageHeaderSearchType', _pageHeaderSearch);
-                       UiSimpleDropdown.close(scope.id);
-               }
-       };
-});
-
-/**
- * Suggestions for page object ids with external response data processing.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Page/Search/Input
- * @extends     module:WoltLabSuite/Core/Ui/Search/Input
- */
-define('WoltLabSuite/Core/Ui/Page/Search/Input',['Core', 'WoltLabSuite/Core/Ui/Search/Input'], function(Core, UiSearchInput) {
-       "use strict";
-       
-       /**
-        * @param       {Element}       element         input element
-        * @param       {Object=}       options         search options and settings
-        * @constructor
-        */
-       function UiPageSearchInput(element, options) { this.init(element, options); }
-       Core.inherit(UiPageSearchInput, UiSearchInput, {
-               init: function(element, options) {
-                       options = Core.extend({
-                               ajax: {
-                                       className: 'wcf\\data\\page\\PageAction'
-                               },
-                               callbackSuccess: null
-                       }, options);
-                       
-                       if (typeof options.callbackSuccess !== 'function') {
-                               throw new Error("Expected a valid callback function for 'callbackSuccess'.");
-                       }
-                       
-                       UiPageSearchInput._super.prototype.init.call(this, element, options);
-                       
-                       this._pageId = 0;
-               },
-               
-               /**
-                * Sets the target page id.
-                * 
-                * @param       {int}   pageId  target page id
-                */
-               setPageId: function(pageId) {
-                       this._pageId = pageId;
-               },
-               
-               _getParameters: function(value) {
-                       var data = UiPageSearchInput._super.prototype._getParameters.call(this, value);
-                       
-                       data.objectIDs = [this._pageId];
-                       
-                       return data;
-               },
-               
-               _ajaxSuccess: function(data) {
-                       this._options.callbackSuccess(data);
-               }
-       });
-       
-       return UiPageSearchInput;
-});
-
-/**
- * Provides access to the lookup function of page handlers, allowing the user to search and
- * select page object ids.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Page/Search/Handler
- */
-define('WoltLabSuite/Core/Ui/Page/Search/Handler',['Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog', './Input'], function(Language, StringUtil, DomUtil, UiDialog, UiPageSearchInput) {
-       "use strict";
-       
-       var _callback = null;
-       var _searchInput = null;
-       var _searchInputLabel = null;
-       var _searchInputHandler = null;
-       var _resultList = null;
-       var _resultListContainer = null;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Page/Search/Handler
-        */
-       return {
-               /**
-                * Opens the lookup overlay for provided page id.
-                * 
-                * @param       {int}           pageId                  page id
-                * @param       {string}        title                   dialog title
-                * @param       {function}      callback                callback function provided with the user-selected object id
-                * @param       {string?}       labelLanguageItem       optional language item name for the search input label
-                */
-               open: function (pageId, title, callback, labelLanguageItem) {
-                       _callback = callback;
-                       
-                       UiDialog.open(this);
-                       UiDialog.setTitle(this, title);
-                       
-                       if (labelLanguageItem) {
-                               _searchInputLabel.textContent = Language.get(labelLanguageItem);
-                       }
-                       else {
-                               _searchInputLabel.textContent = Language.get('wcf.page.pageObjectID.search.terms');
-                       }
-                       
-                       this._getSearchInputHandler().setPageId(pageId);
-               },
-               
-               /**
-                * Builds the result list.
-                * 
-                * @param       {Object}        data            AJAX response data
-                * @protected
-                */
-               _buildList: function(data) {
-                       this._resetList();
-                       
-                       // no matches
-                       if (!Array.isArray(data.returnValues) || data.returnValues.length === 0) {
-                               elInnerError(_searchInput, Language.get('wcf.page.pageObjectID.search.noResults'));
-                               
-                               return;
-                       }
-                       
-                       var image, item, listItem;
-                       for (var i = 0, length = data.returnValues.length; i < length; i++) {
-                               item = data.returnValues[i];
-                               image = item.image;
-                               if (/^fa-/.test(image)) {
-                                       image = '<span class="icon icon48 ' + image + ' pointer jsTooltip" title="' + Language.get('wcf.global.select') + '"></span>';
-                               }
-                               
-                               listItem = elCreate('li');
-                               elData(listItem, 'object-id', item.objectID);
-                               
-                               listItem.innerHTML = '<div class="box48">'
-                                       + image
-                                       + '<div>'
-                                               + '<div class="containerHeadline">'
-                                                       + '<h3><a href="' + StringUtil.escapeHTML(item.link) + '">' + StringUtil.escapeHTML(item.title) + '</a></h3>'
-                                                       + (item.description ? '<p>' + item.description + '</p>' : '')
-                                               + '</div>'
-                                       + '</div>'
-                               + '</div>';
-                               
-                               listItem.addEventListener(WCF_CLICK_EVENT, this._click.bind(this));
-                               
-                               _resultList.appendChild(listItem);
-                       }
-                       
-                       elShow(_resultListContainer);
-               },
-               
-               /**
-                * Resets the list and removes any error elements.
-                * 
-                * @protected
-                */
-               _resetList: function() {
-                       elInnerError(_searchInput, false);
-                       
-                       _resultList.innerHTML = '';
-                       
-                       elHide(_resultListContainer);
-               },
-               
-               /**
-                * Initializes the search input handler and returns the instance.
-                * 
-                * @returns     {UiPageSearchInput}     search input handler
-                * @protected
-                */
-               _getSearchInputHandler: function() {
-                       if (_searchInputHandler === null) {
-                               var callback = this._buildList.bind(this);
-                               _searchInputHandler = new UiPageSearchInput(elById('wcfUiPageSearchInput'), {
-                                       callbackSuccess: callback
-                               });
-                       }
-                       
-                       return _searchInputHandler;
-               },
-               
-               /**
-                * Handles clicks on the item unless the click occurred directly on a link.
-                * 
-                * @param       {Event}         event           event object
-                * @protected
-                */
-               _click: function(event) {
-                       if (event.target.nodeName === 'A') {
-                               return;
-                       }
-                       
-                       event.stopPropagation();
-                       
-                       _callback(elData(event.currentTarget, 'object-id'));
-                       UiDialog.close(this);
-               },
-               
-               _dialogSetup: function() {
-                       return {
-                               id: 'wcfUiPageSearchHandler',
-                               options: {
-                                       onShow: function() {
-                                               if (_searchInput === null) {
-                                                       _searchInput = elById('wcfUiPageSearchInput');
-                                                       _searchInputLabel = _searchInput.parentNode.previousSibling.childNodes[0];
-                                                       _resultList = elById('wcfUiPageSearchResultList');
-                                                       _resultListContainer = elById('wcfUiPageSearchResultListContainer');
-                                               }
-                                               
-                                               // clear search input
-                                               _searchInput.value = '';
-                                               
-                                               // reset results
-                                               elHide(_resultListContainer);
-                                               _resultList.innerHTML = '';
-                                               
-                                               _searchInput.focus();
-                                       },
-                                       title: ''
-                               },
-                               source: '<div class="section">'
-                                               + '<dl>'
-                                                       + '<dt><label for="wcfUiPageSearchInput">' + Language.get('wcf.page.pageObjectID.search.terms') + '</label></dt>'
-                                                       + '<dd>'
-                                                               + '<input type="text" id="wcfUiPageSearchInput" class="long">'
-                                                       + '</dd>'
-                                               + '</dl>'
-                                       + '</div>'
-                                       + '<section id="wcfUiPageSearchResultListContainer" class="section sectionContainerList">'
-                                               + '<header class="sectionHeader">'
-                                                       + '<h2 class="sectionTitle">' + Language.get('wcf.page.pageObjectID.search.results') + '</h2>'
-                                               + '</header>'
-                                               + '<ul id="wcfUiPageSearchResultList" class="containerList wcfUiPageSearchResultList"></ul>'
-                                       + '</section>'
-                       };
-               }
-       };
-});
-
-/**
- * Handles the reaction list in the user profile. 
- *
- * @author     Joshua Ruesweg
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Reaction/Profile/Loader
- * @since       5.2
- */
-define('WoltLabSuite/Core/Ui/Reaction/Profile/Loader',['Ajax', 'Core', 'Language'], function(Ajax, Core, Language) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function UiReactionProfileLoader(userID, firstReactionTypeID) { this.init(userID, firstReactionTypeID); }
-       UiReactionProfileLoader.prototype = {
-               /**
-                * Initializes a new ReactionListLoader object.
-                *
-                * @param       integer         userID
-                */
-               init: function(userID, firstReactionTypeID) {
-                       this._container = elById('likeList');
-                       this._userID = userID;
-                       this._reactionTypeID = firstReactionTypeID;
-                       this._targetType = 'received';
-                       this._options = {
-                               parameters: []
-                       };
-                       
-                       if (!this._userID) {
-                               throw new Error("[WoltLabSuite/Core/Ui/Reaction/Profile/Loader] Invalid parameter 'userID' given.");
-                       }
-                       
-                       if (!this._reactionTypeID) {
-                               throw new Error("[WoltLabSuite/Core/Ui/Reaction/Profile/Loader] Invalid parameter 'firstReactionTypeID' given.");
-                       }
-                       
-                       var loadButtonList = elCreate('li');
-                       loadButtonList.className = 'likeListMore showMore';
-                       this._noMoreEntries = elCreate('small');
-                       this._noMoreEntries.innerHTML = Language.get('wcf.like.reaction.noMoreEntries');
-                       this._noMoreEntries.style.display = 'none';
-                       loadButtonList.appendChild(this._noMoreEntries);
-                       
-                       this._loadButton = elCreate('button');
-                       this._loadButton.className = 'small';
-                       this._loadButton.innerHTML = Language.get('wcf.like.reaction.more');
-                       this._loadButton.addEventListener(WCF_CLICK_EVENT, this._loadReactions.bind(this));
-                       this._loadButton.style.display = 'none';
-                       loadButtonList.appendChild(this._loadButton);
-                       this._container.appendChild(loadButtonList);
-                       
-                       if (elBySel('#likeList > li').length === 2) {
-                               this._noMoreEntries.style.display = '';
-                       }
-                       else {
-                               this._loadButton.style.display = '';
-                       }
-                       
-                       this._setupReactionTypeButtons();
-                       this._setupTargetTypeButtons();
-               },
-               
-               /**
-                * Set up the reaction type buttons. 
-                */
-               _setupReactionTypeButtons: function() {
-                       var element, elements = elBySelAll('#reactionType .button');
-                       for (var i = 0, length = elements.length; i < length; i++) {
-                               element = elements[i];
-                               element.addEventListener(WCF_CLICK_EVENT, this._changeReactionTypeValue.bind(this, ~~elData(element, 'reaction-type-id')));
-                       }
-               },
-               
-               /**
-                * Set up the target type buttons.
-                */
-               _setupTargetTypeButtons: function() {
-                       var element, elements = elBySelAll('#likeType .button');
-                       for (var i = 0, length = elements.length; i < length; i++) {
-                               element = elements[i];
-                               element.addEventListener(WCF_CLICK_EVENT, this._changeTargetType.bind(this, elData(element, 'like-type')));
-                       }
-               },
-               
-               /**
-                * Changes the reaction target type (given or received) and reload the entire element.
-                * 
-                * @param       {string}           targetType
-                */
-               _changeTargetType: function(targetType) {
-                       if (targetType !== 'given' && targetType !== 'received') {
-                               throw new Error("[WoltLabSuite/Core/Ui/Reaction/Profile/Loader] Invalid parameter 'targetType' given.");
-                       }
-                       
-                       if (targetType !== this._targetType) {
-                               // remove old active state
-                               elBySel('#likeType .button.active').classList.remove('active');
-                               
-                               // add active status to new button 
-                               elBySel('#likeType .button[data-like-type="'+ targetType +'"]').classList.add('active');
-                               
-                               this._targetType = targetType;
-                               this._reload();
-                       }
-               },
-               
-               /**
-                * Changes the reaction type value and reload the entire element. 
-                * 
-                * @param       {int}           reactionTypeID
-                */
-               _changeReactionTypeValue: function(reactionTypeID) {
-                       if (this._reactionTypeID !== reactionTypeID) {
-                               // remove old active state
-                               elBySel('#reactionType .button.active').classList.remove('active');
-                               
-                               // add active status to new button 
-                               elBySel('#reactionType .button[data-reaction-type-id="'+ reactionTypeID +'"]').classList.add('active');
-                               
-                               this._reactionTypeID = reactionTypeID;
-                               this._reload();
-                       }
-               },
-               
-               /**
-                * Handles reload.
-                */
-               _reload: function() {
-                       var elements = elBySelAll('#likeList > li:not(:first-child):not(:last-child)');
-                       
-                       for (var i = 0, length = elements.length; i < length; i++) {
-                               this._container.removeChild(elements[i]);
-                       }
-                       
-                       elData(this._container, 'last-like-time', 0);
-                       
-                       this._loadReactions();
-               },
-               
-               /**
-                * Load a list of reactions. 
-                */
-               _loadReactions: function() {
-                       this._options.parameters.userID = this._userID;
-                       this._options.parameters.lastLikeTime = elData(this._container, 'last-like-time');
-                       this._options.parameters.targetType = this._targetType;
-                       this._options.parameters.reactionTypeID = this._reactionTypeID;
-                       
-                       Ajax.api(this, {
-                               parameters: this._options.parameters
-                       });
-               },
-               
-               _ajaxSuccess: function(data) {
-                       if (data.returnValues.template) {
-                               elBySel('#likeList > li:nth-last-child(1)').insertAdjacentHTML('beforebegin', data.returnValues.template);
-                               
-                               elData(this._container, 'last-like-time', data.returnValues.lastLikeTime);
-                               this._noMoreEntries.style.display = 'none';
-                               this._loadButton.style.display = '';
-                       }
-                       else {
-                               this._noMoreEntries.style.display = '';
-                               this._loadButton.style.display = 'none';
-                       }
-               },
-               
-               _ajaxSetup: function() {
-                       return {
-                               data: {
-                                       actionName: 'load',
-                                       className: '\\wcf\\data\\reaction\\ReactionAction'
-                               }
-                       };
-               }
-       };
-       
-       return UiReactionProfileLoader;
-});
-
-define('WoltLabSuite/Core/Ui/User/Activity/Recent',['Ajax', 'Language', 'Dom/Util'], function(Ajax, Language, DomUtil) {
-       "use strict";
-       
-       function UiUserActivityRecent(containerId) { this.init(containerId); }
-       UiUserActivityRecent.prototype = {
-               init: function (containerId) {
-                       this._containerId = containerId;
-                       var container = elById(this._containerId);
-                       this._list = elBySel('.recentActivityList', container);
-                       
-                       var showMoreItem = elCreate('li');
-                       showMoreItem.className = 'showMore';
-                       if (this._list.childElementCount) {
-                               showMoreItem.innerHTML = '<button class="small">' + Language.get('wcf.user.recentActivity.more') + '</button>';
-                               showMoreItem.children[0].addEventListener(WCF_CLICK_EVENT, this._showMore.bind(this));
-                       }
-                       else {
-                               showMoreItem.innerHTML = '<small>' + Language.get('wcf.user.recentActivity.noMoreEntries') + '</small>';
-                       }
-                       
-                       this._list.appendChild(showMoreItem);
-                       this._showMoreItem = showMoreItem;
-                       
-                       elBySelAll('.jsRecentActivitySwitchContext .button', container, (function (button) {
-                               button.addEventListener(WCF_CLICK_EVENT, (function (event) {
-                                       event.preventDefault();
-                                       
-                                       if (!button.classList.contains('active')) {
-                                               this._switchContext();
-                                       }
-                               }).bind(this));
-                       }).bind(this));
-               },
-               
-               _showMore: function (event) {
-                       event.preventDefault();
-                       
-                       this._showMoreItem.children[0].disabled = true;
-                       
-                       Ajax.api(this, {
-                               actionName: 'load',
-                               parameters: {
-                                       boxID: ~~elData(this._list, 'box-id'),
-                                       filteredByFollowedUsers: elDataBool(this._list, 'filtered-by-followed-users'),
-                                       lastEventId: elData(this._list, 'last-event-id'),
-                                       lastEventTime: elData(this._list, 'last-event-time'),
-                                       userID: ~~elData(this._list, 'user-id')
-                               }
-                       });
-               },
-               
-               _switchContext: function() {
-                       Ajax.api(
-                               this,
-                               {
-                                       actionName: 'switchContext'
-                               },
-                               (function () {
-                                       window.location.hash = '#' + this._containerId;
-                                       window.location.reload();
-                               }).bind(this)
-                       );
-               },
-               
-               _ajaxSuccess: function(data) {
-                       if (data.returnValues.template) {
-                               DomUtil.insertHtml(data.returnValues.template, this._showMoreItem, 'before');
-                               
-                               elData(this._list, 'last-event-time', data.returnValues.lastEventTime);
-                               elData(this._list, 'last-event-id', data.returnValues.lastEventID);
-                               
-                               this._showMoreItem.children[0].disabled = false;
-                       }
-                       else {
-                               this._showMoreItem.innerHTML = '<small>' + Language.get('wcf.user.recentActivity.noMoreEntries') + '</small>';
-                       }
-               },
-               
-               _ajaxSetup: function () {
-                       return {
-                               data: {
-                                       className: 'wcf\\data\\user\\activity\\event\\UserActivityEventAction'
-                               }
-                       };
-               }
-       };
-       
-       return UiUserActivityRecent;
-});
-
-/**
- * Deletes the current user cover photo.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/User/CoverPhoto/Delete
- */
-define('WoltLabSuite/Core/Ui/User/CoverPhoto/Delete',['Ajax', 'EventHandler', 'Language', 'Ui/Confirmation', 'Ui/Notification'], function (Ajax, EventHandler, Language, UiConfirmation, UiNotification) {
-       "use strict";
-       
-       var _button;
-       var _userId = 0;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/User/CoverPhoto/Delete
-        */
-       return {
-               /**
-                * Initializes the delete handler and enables the delete button on upload.
-                */
-               init: function (userId) {
-                       _button = elBySel('.jsButtonDeleteCoverPhoto');
-                       _button.addEventListener(WCF_CLICK_EVENT, this._click.bind(this));
-                       _userId = userId;
-                       
-                       EventHandler.add('com.woltlab.wcf.user', 'coverPhoto', function (data) {
-                               if (typeof data.url === 'string' && data.url.length > 0) {
-                                       elShow(_button.parentNode);
-                               }
-                       });
-               },
-               
-               /**
-                * Handles clicks on the delete button.
-                * 
-                * @param {Event} event
-                * @protected
-                */
-               _click: function (event) {
-                       event.preventDefault();
-                       
-                       UiConfirmation.show({
-                               confirm: Ajax.api.bind(Ajax, this),
-                               message: Language.get('wcf.user.coverPhoto.delete.confirmMessage')
-                       });
-               },
-               
-               _ajaxSuccess: function (data) {
-                       elBySel('.userProfileCoverPhoto').style.setProperty('background-image', 'url(' + data.returnValues.url + ')', '');
-                       
-                       elHide(_button.parentNode);
-                       
-                       UiNotification.show();
-               },
-               
-               _ajaxSetup: function () {
-                       return {
-                               data: {
-                                       actionName: 'deleteCoverPhoto',
-                                       className: 'wcf\\data\\user\\UserProfileAction',
-                                       parameters: {
-                                               userID: _userId
-                                       }
-                               }
-                       };
-               }
-       };
-});
-
-/**
- * Uploads the user cover photo via AJAX.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/User/CoverPhoto/Upload
- */
-define('WoltLabSuite/Core/Ui/User/CoverPhoto/Upload',['Core', 'EventHandler', 'Upload', 'Ui/Notification', 'Ui/Dialog'], function(Core, EventHandler, Upload, UiNotification, UiDialog) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function UiUserCoverPhotoUpload(userId) {
-               Upload.call(this, 'coverPhotoUploadButtonContainer', 'coverPhotoUploadPreview', {
-                       action: 'uploadCoverPhoto',
-                       className: 'wcf\\data\\user\\UserProfileAction'
-               });
-               
-               this._userId = userId;
-       }
-       Core.inherit(UiUserCoverPhotoUpload, Upload, {
-               _getParameters: function() {
-                       return {
-                               userID: this._userId
-                       };
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Upload#_success
-                */
-               _success: function(uploadId, data) {
-                       // remove or display the error message
-                       elInnerError(this._button, data.returnValues.errorMessage);
-                       
-                       // remove the upload progress
-                       this._target.innerHTML = '';
-                       
-                       if (data.returnValues.url) {
-                               elBySel('.userProfileCoverPhoto').style.setProperty('background-image', 'url(' + data.returnValues.url + ')', '');
-                               
-                               UiDialog.close('userProfileCoverPhotoUpload');
-                               UiNotification.show();
-                               
-                               EventHandler.fire('com.woltlab.wcf.user', 'coverPhoto', {
-                                       url: data.returnValues.url
-                               });
-                       }
-               }
-       });
-       
-       return UiUserCoverPhotoUpload;
-});
-
-/**
- * Handles the user trophy dialog.
- *
- * @author     Joshua Ruesweg
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/User/Trophy/List
- */
-define('WoltLabSuite/Core/Ui/User/Trophy/List',['Ajax', 'Core', 'Dictionary', 'Dom/Util', 'Ui/Dialog', 'WoltLabSuite/Core/Ui/Pagination', 'Dom/ChangeListener', 'List'], function(Ajax, Core, Dictionary, DomUtil, UiDialog, UiPagination, DomChangeListener, List) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function UiUserTrophyList() { this.init(); }
-       UiUserTrophyList.prototype = {
-               /**
-                * Initializes the user trophy list.
-                */
-               init: function() {
-                       this._cache = new Dictionary();
-                       this._knownElements = new List();
-                       
-                       this._options = {
-                               className: 'wcf\\data\\user\\trophy\\UserTrophyAction',
-                               parameters: {}
-                       };
-                       
-                       this._rebuild();
-                       
-                       DomChangeListener.add('WoltLabSuite/Core/Ui/User/Trophy/List', this._rebuild.bind(this));
-               },
-               
-               /**
-                * Adds event userTrophyOverlayList elements.
-                */
-               _rebuild: function() {
-                       elBySelAll('.userTrophyOverlayList', undefined, (function (element) {
-                               if (!this._knownElements.has(element)) {
-                                       element.addEventListener(WCF_CLICK_EVENT, this._open.bind(this, elData(element, 'user-id')));
-                                       
-                                       this._knownElements.add(element);
-                               }
-                       }).bind(this));
-               },
-               
-               /**
-                * Opens the user trophy list for a specific user.
-                *
-                * @param       {int}           userId
-                * @param       {Event}         event           event object
-                */
-               _open: function(userId, event) {
-                       event.preventDefault();
-                       
-                       this._currentPageNo = 1;
-                       this._currentUser = userId;
-                       this._showPage();
-               },
-               
-               /**
-                * Shows the current or given page.
-                *
-                * @param       {int=}          pageNo          page number
-                */
-               _showPage: function(pageNo) {
-                       if (pageNo !== undefined) {
-                               this._currentPageNo = pageNo;
-                       }
-                       
-                       if (this._cache.has(this._currentUser)) {
-                               // validate pageNo
-                               if (this._cache.get(this._currentUser).get('pageCount') !== 0 && (this._currentPageNo < 1 || this._currentPageNo > this._cache.get(this._currentUser).get('pageCount'))) {
-                                       throw new RangeError("pageNo must be between 1 and " + this._cache.get(this._currentUser).get('pageCount') + " (" + this._currentPageNo + " given).");
-                               }
-                       }
-                       else {
-                               // init user page cache
-                               this._cache.set(this._currentUser, new Dictionary());
-                       }
-                       
-                       if (this._cache.get(this._currentUser).has(this._currentPageNo)) {
-                               var dialog = UiDialog.open(this, this._cache.get(this._currentUser).get(this._currentPageNo));
-                               UiDialog.setTitle('userTrophyListOverlay', this._cache.get(this._currentUser).get('title'));
-                               
-                               if (this._cache.get(this._currentUser).get('pageCount') > 1) {
-                                       var element = elBySel('.jsPagination', dialog.content);
-                                       if (element !== null) {
-                                               new UiPagination(element, {
-                                                       activePage: this._currentPageNo,
-                                                       maxPage: this._cache.get(this._currentUser).get('pageCount'),
-                                                       callbackSwitch: this._showPage.bind(this)
-                                               });
-                                       }
-                               }
-                       }
-                       else {
-                               this._options.parameters.pageNo = this._currentPageNo;
-                               this._options.parameters.userID = this._currentUser;
-                               
-                               Ajax.api(this, {
-                                       parameters: this._options.parameters
-                               });
-                       }
-               },
-               
-               _ajaxSuccess: function(data) {
-                       if (data.returnValues.pageCount !== undefined) {
-                               this._cache.get(this._currentUser).set('pageCount', ~~data.returnValues.pageCount);
-                       }
-                       
-                       this._cache.get(this._currentUser).set(this._currentPageNo, data.returnValues.template);
-                       this._cache.get(this._currentUser).set('title', data.returnValues.title);
-                       this._showPage();
-               },
-               
-               _ajaxSetup: function() {
-                       return {
-                               data: {
-                                       actionName: 'getGroupedUserTrophyList',
-                                       className: this._options.className
-                               }
-                       };
-               },
-               
-               _dialogSetup: function() {
-                       return {
-                               id: 'userTrophyListOverlay',
-                               options: {
-                                       title: ""
-                               },
-                               source: null
-                       };
-               }
-       };
-       
-       return UiUserTrophyList;
-});
-
-/**
- * Handles the JavaScript part of the label form field.
- * 
- * @author     Alexander Ebert, Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Controller/Label
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Controller/Label',['Core', 'Dom/Util', 'Language', 'Ui/SimpleDropdown'], function(Core, DomUtil, Language, UiSimpleDropdown) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function FormBuilderFieldLabel(fielId, labelId, options) {
-               this.init(fielId, labelId, options);
-       };
-       FormBuilderFieldLabel.prototype = {
-               /**
-                * Initializes the label form field.
-                * 
-                * @param       {string}        fieldId         id of the relevant form builder field
-                * @param       {integer}       labelId         id of the currently selected label
-                * @param       {object}        options         additional label options
-                */
-               init: function(fieldId, labelId, options) {
-                       this._formFieldContainer = elById(fieldId + 'Container');
-                       this._labelChooser = elByClass('labelChooser', this._formFieldContainer)[0];
-                       this._options = Core.extend({
-                               forceSelection: false,
-                               showWithoutSelection: false
-                       }, options);
-                       
-                       this._input = elCreate('input');
-                       this._input.type = 'hidden';
-                       this._input.id = fieldId;
-                       this._input.name = fieldId;
-                       this._input.value = ~~labelId;
-                       this._formFieldContainer.appendChild(this._input);
-                       
-                       var labelChooserId = DomUtil.identify(this._labelChooser);
-                       
-                       // init dropdown
-                       var dropdownMenu = UiSimpleDropdown.getDropdownMenu(labelChooserId);
-                       if (dropdownMenu === null) {
-                               UiSimpleDropdown.init(elByClass('dropdownToggle', this._labelChooser)[0]);
-                               dropdownMenu = UiSimpleDropdown.getDropdownMenu(labelChooserId);
-                       }
-                       
-                       var additionalOptionList = null;
-                       if (this._options.showWithoutSelection || !this._options.forceSelection) {
-                               additionalOptionList = elCreate('ul');
-                               dropdownMenu.appendChild(additionalOptionList);
-                               
-                               var dropdownDivider = elCreate('li');
-                               dropdownDivider.className = 'dropdownDivider';
-                               additionalOptionList.appendChild(dropdownDivider);
-                       }
-                       
-                       if (this._options.showWithoutSelection) {
-                               var listItem = elCreate('li');
-                               elData(listItem, 'label-id', -1);
-                               this._blockScroll(listItem);
-                               additionalOptionList.appendChild(listItem);
-                               
-                               var span = elCreate('span');
-                               listItem.appendChild(span);
-                               
-                               var label = elCreate('span');
-                               label.className = 'badge label';
-                               label.innerHTML = Language.get('wcf.label.withoutSelection');
-                               span.appendChild(label);
-                       }
-                       
-                       if (!this._options.forceSelection) {
-                               var listItem = elCreate('li');
-                               elData(listItem, 'label-id', 0);
-                               this._blockScroll(listItem);
-                               additionalOptionList.appendChild(listItem);
-                               
-                               var span = elCreate('span');
-                               listItem.appendChild(span);
-                               
-                               var label = elCreate('span');
-                               label.className = 'badge label';
-                               label.innerHTML = Language.get('wcf.label.none');
-                               span.appendChild(label);
-                       }
-                       
-                       elBySelAll('li:not(.dropdownDivider)', dropdownMenu, function(listItem) {
-                               listItem.addEventListener('click', this._click.bind(this));
-                               
-                               if (labelId) {
-                                       if (~~elData(listItem, 'label-id') === labelId) {
-                                               this._selectLabel(listItem);
-                                       }
-                               }
-                       }.bind(this));
-               },
-               
-               /**
-                * Blocks page scrolling for the given element.
-                * 
-                * @param       {HTMLElement}           element
-                */
-               _blockScroll: function(element) {
-                       element.addEventListener(
-                               'wheel',
-                               function(event) {
-                                       event.preventDefault();
-                               },
-                               {
-                                       passive: false
-                               }
-                       );
-               },
-               
-               /**
-                * Select a label after clicking on it.
-                * 
-                * @param       {Event}         event   click event in label selection dropdown
-                */
-               _click: function(event) {
-                       event.preventDefault();
-                       
-                       this._selectLabel(event.currentTarget, false);
-               },
-               
-               /**
-                * Selects the given label.
-                * 
-                * @param       {HTMLElement}   label
-                */
-               _selectLabel: function(label) {
-                       // save label
-                       var labelId = elData(label, 'label-id');
-                       if (!labelId) {
-                               labelId = 0;
-                       }
-                       
-                       // replace button with currently selected label
-                       var displayLabel = elBySel('span > span', label);
-                       var button = elBySel('.dropdownToggle > span', this._labelChooser);
-                       button.className = displayLabel.className;
-                       button.textContent = displayLabel.textContent;
-                       
-                       this._input.value = labelId;
-               }
-       };
-       
-       return FormBuilderFieldLabel;
-});
-
-/**
- * Handles the JavaScript part of the rating form field.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Controller/Rating
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Controller/Rating',['Dictionary', 'Environment'], function(Dictionary, Environment) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function FormBuilderFieldRating(fieldId, value, activeCssClasses, defaultCssClasses) {
-               this.init(fieldId, value, activeCssClasses, defaultCssClasses);
-       };
-       FormBuilderFieldRating.prototype = {
-               /**
-                * Initializes the rating form field.
-                * 
-                * @param       {string}        fieldId                 id of the relevant form builder field
-                * @param       {integer}       value                   current value of the field
-                * @param       {string[]}      activeCssClasses        CSS classes for the active state of rating elements
-                * @param       {string[]}      defaultCssClasses       CSS classes for the default state of rating elements
-                */
-               init: function(fieldId, value, activeCssClasses, defaultCssClasses) {
-                       this._field = elBySel('#' + fieldId + 'Container');
-                       if (this._field === null) {
-                               throw new Error("Unknown field with id '" + fieldId + "'");
-                       }
-                       
-                       this._input = elCreate('input');
-                       this._input.id = fieldId;
-                       this._input.name = fieldId;
-                       this._input.type = 'hidden';
-                       this._input.value = value;
-                       this._field.appendChild(this._input);
-                       
-                       this._activeCssClasses = activeCssClasses;
-                       this._defaultCssClasses = defaultCssClasses;
-                       
-                       this._ratingElements = new Dictionary();
-                       
-                       var ratingList = elBySel('.ratingList', this._field);
-                       ratingList.addEventListener('mouseleave', this._restoreRating.bind(this));
-                       
-                       elBySelAll('li', ratingList, function(listItem) {
-                               if (listItem.classList.contains('ratingMetaButton')) {
-                                       listItem.addEventListener('click', this._metaButtonClick.bind(this));
-                                       listItem.addEventListener('mouseenter', this._restoreRating.bind(this));
-                               }
-                               else {
-                                       this._ratingElements.set(~~elData(listItem, 'rating'), listItem);
-                                       
-                                       listItem.addEventListener('click', this._listItemClick.bind(this));
-                                       listItem.addEventListener('mouseenter', this._listItemMouseEnter.bind(this));
-                                       listItem.addEventListener('mouseleave', this._listItemMouseLeave.bind(this));
-                               }
-                       }.bind(this));
-               },
-               
-               /**
-                * Saves the rating associated with the clicked rating element.
-                * 
-                * @param       {Event}         event   rating element `click` event
-                */
-               _listItemClick: function(event) {
-                       this._input.value = ~~elData(event.currentTarget, 'rating');
-                       
-                       if (Environment.platform() !== 'desktop') {
-                               this._restoreRating();
-                       }
-               },
-               
-               /**
-                * Updates the rating UI when hovering over a rating element.
-                * 
-                * @param       {Event}         event   rating element `mouseenter` event
-                */
-               _listItemMouseEnter: function(event) {
-                       var currentRating = elData(event.currentTarget, 'rating');
-                       
-                       this._ratingElements.forEach(function(ratingElement, rating) {
-                               var icon = elByClass('icon', ratingElement)[0];
-                               
-                               this._toggleIcon(icon, ~~rating <= ~~currentRating);
-                       }.bind(this));
-               },
-               
-               /**
-                * Updates the rating UI when leaving a rating element by changing all rating elements
-                * to their default state.
-                */
-               _listItemMouseLeave: function() {
-                       this._ratingElements.forEach(function(ratingElement) {
-                               var icon = elByClass('icon', ratingElement)[0];
-                               
-                               this._toggleIcon(icon, false);
-                       }.bind(this));
-               },
-               
-               /**
-                * Handles clicks on meta buttons.
-                * 
-                * @param       {Event}         event   meta button `click` event
-                */
-               _metaButtonClick: function(event) {
-                       if (elData(event.currentTarget, 'action') === 'removeRating') {
-                               this._input.value = '';
-                               
-                               this._listItemMouseLeave();
-                       }
-               },
-               
-               /**
-                * Updates the rating UI by changing the rating elements to the stored rating state.
-                */
-               _restoreRating: function() {
-                       this._ratingElements.forEach(function(ratingElement, rating) {
-                               var icon = elByClass('icon', ratingElement)[0];
-                               
-                               this._toggleIcon(icon, ~~rating <= ~~this._input.value);
-                       }.bind(this));
-               },
-               
-               /**
-                * Toggles the state of the given icon based on the given state parameter.
-                * 
-                * @param       {HTMLElement}   icon            toggled icon
-                * @param       {boolean}       active          is `true` if icon will be changed to `active` state, otherwise changed to `default` state
-                */
-               _toggleIcon: function(icon, active) {
-                       active = active || false;
-                       
-                       if (active) {
-                               for (var i = 0; i < this._defaultCssClasses.length; i++) {
-                                       icon.classList.remove(this._defaultCssClasses[i]);
-                               }
-                               
-                               for (var i = 0; i < this._activeCssClasses.length; i++) {
-                                       icon.classList.add(this._activeCssClasses[i]);
-                               }
-                       }
-                       else {
-                               for (var i = 0; i < this._activeCssClasses.length; i++) {
-                                       icon.classList.remove(this._activeCssClasses[i]);
-                               }
-                               
-                               for (var i = 0; i < this._defaultCssClasses.length; i++) {
-                                       icon.classList.add(this._defaultCssClasses[i]);
-                               }
-                       }
-               }
-       };
-       
-       return FormBuilderFieldRating;
-});
-
-/**
- * Abstract implementation of a form field dependency.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract',['./Manager'], function(DependencyManager) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function Abstract(dependentElementId, fieldId) {
-               this.init(dependentElementId, fieldId);
-       };
-       Abstract.prototype = {
-               /**
-                * Checks if the dependency is met.
-                * 
-                * @abstract
-                */
-               checkDependency: function() {
-                       throw new Error("Missing implementation of WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract.checkDependency!");
-               },
-               
-               /**
-                * Return the node whose availability depends on the value of a field.
-                * 
-                * @return      {HtmlElement}   dependent node
-                */
-               getDependentNode: function() {
-                       return this._dependentElement;
-               },
-               
-               /**
-                * Returns the field the availability of the element dependents on.
-                * 
-                * @return      {HtmlElement}   field controlling element availability
-                */
-               getField: function() {
-                       return this._field;
-               },
-               
-               /**
-                * Returns all fields requiring `change` event listeners for this
-                * dependency to be properly resolved.
-                * 
-                * @return      {HtmlElement[]}         fields to register event listeners on
-                */
-               getFields: function() {
-                       return this._fields;
-               },
-               
-               /**
-                * Initializes the new dependency object.
-                * 
-                * @param       {string}        dependentElementId      id of the (container of the) dependent element
-                * @param       {string}        fieldId                 id of the field controlling element availability
-                * 
-                * @throws      {Error}                                 if either depenent element id or field id are invalid
-                */
-               init: function(dependentElementId, fieldId) {
-                       this._dependentElement = elById(dependentElementId);
-                       if (this._dependentElement === null) {
-                               throw new Error("Unknown dependent element with container id '" + dependentElementId + "Container'.");
-                       }
-                       
-                       this._field = elById(fieldId);
-                       if (this._field === null) {
-                               this._fields = [];
-                               elBySelAll('input[type=radio][name=' + fieldId + ']', undefined, function(field) {
-                                       this._fields.push(field);
-                               }.bind(this));
-                               
-                               if (!this._fields.length) {
-                                       throw new Error("Unknown field with id '" + fieldId + "'.");
-                               }
-                       }
-                       else {
-                               this._fields = [this._field];
-                               
-                               // handle special case of boolean form fields that have to form fields
-                               if (this._field.tagName === 'INPUT' && this._field.type === 'radio' && elData(this._field, 'no-input-id') !== '') {
-                                       this._noField = elById(elData(this._field, 'no-input-id'));
-                                       if (this._noField === null) {
-                                               throw new Error("Cannot find 'no' input field for input field '" + fieldId + "'");
-                                       }
-                                       
-                                       this._fields.push(this._noField);
-                               }
-                       }
-                       
-                       DependencyManager.addDependency(this);
-               }
-       };
-       
-       return Abstract;
-});
-
-/**
- * Form field dependency implementation that requires the value of a field not to be empty.
- *
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Dependency/NonEmpty
- * @see        module:WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Dependency/NonEmpty',['./Abstract', 'Core'], function(Abstract, Core) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function NonEmpty(dependentElementId, fieldId) {
-               this.init(dependentElementId, fieldId);
-       };
-       Core.inherit(NonEmpty, Abstract, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract#checkDependency
-                */
-               checkDependency: function() {
-                       switch (this._field.tagName) {
-                               case 'INPUT':
-                                       switch (this._field.type) {
-                                               case 'checkbox':
-                                                       // TODO: check if working
-                                                       return this._field.checked;
-                                               
-                                               case 'radio':
-                                                       if (this._noField && this._noField.checked) {
-                                                               return false;
-                                                       }
-                                                       
-                                                       return this._field.checked;
-                                               
-                                               default:
-                                                       return this._field.value.trim().length !== 0;
-                                       }
-                               
-                               case 'SELECT':
-                                       // TODO: check if working for multiselect
-                                       return this._field.value.length !== 0;
-                               
-                               case 'TEXTAREA':
-                                       // TODO: check if working
-                                       return this._field.value.trim().length !== 0;
-                       }
-               }
-       });
-       
-       return NonEmpty;
-});
-
-/**
- * Form field dependency implementation that requires a field to have a certain value.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Dependency/Value
- * @see        module:WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Dependency/Value',['./Abstract', 'Core', './Manager'], function(Abstract, Core, Manager) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function Value(dependentElementId, fieldId, isNegated) {
-               this.init(dependentElementId, fieldId);
-               
-               this._isNegated = false;
-       };
-       Core.inherit(Value, Abstract, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract#checkDependency
-                */
-               checkDependency: function() {
-                       if (!this._values) {
-                               throw new Error("Values have not been set.");
-                       }
-                       
-                       var value;
-                       if (this._field) {
-                               if (Manager.isHiddenByDependencies(this._field)) {
-                                       return false;
-                               }
-                               
-                               value = this._field.value;
-                       }
-                       else {
-                               for (var i = 0, length = this._fields.length, field; i < length; i++) {
-                                       field = this._fields[i];
-                                       
-                                       if (field.checked) {
-                                               if (Manager.isHiddenByDependencies(field)) {
-                                                       return false;
-                                               }
-                                               
-                                               value = field.value;
-                                               
-                                               break;
-                                       }
-                               }
-                       }
-                       
-                       // do not use `Array.prototype.indexOf()` as we use a weak comparision
-                       for (var i = 0, length = this._values.length; i < length; i++) {
-                               if (this._values[i] == value) {
-                                       if (this._isNegated) {
-                                               return false;
-                                       }
-                                       
-                                       return true;
-                               }
-                       }
-                       
-                       if (this._isNegated) {
-                               return true;
-                       }
-                       
-                       return false;
-               },
-               
-               /**
-                * Sets if the field value may not have any of the set values.
-                * 
-                * @param       {bool}          negate
-                * @return      {WoltLabSuite/Core/Form/Builder/Field/Dependency/Value}
-                */
-               negate: function(negate) {
-                       this._isNegated = negate;
-                       
-                       return this;
-               },
-               
-               /**
-                * Sets the possible values the field may have for the dependency to be met.
-                * 
-                * @param       {array}         values
-                * @return      {WoltLabSuite/Core/Form/Builder/Field/Dependency/Value}
-                */
-               values: function(values) {
-                       this._values = values;
-                       
-                       return this;
-               }
-       });
-       
-       return Value;
-});
-
-/**
- * Data handler for a content language form builder field in an Ajax form.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Language/ContentLanguage
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Language/ContentLanguage',['Core', 'WoltLabSuite/Core/Language/Chooser', '../Value'], function(Core, LanguageChooser, FormBuilderFieldValue) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function FormBuilderFieldContentLanguage(fieldId) {
-               this.init(fieldId);
-       };
-       Core.inherit(FormBuilderFieldContentLanguage, FormBuilderFieldValue, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#destroy
-                */
-               destroy: function() {
-                       LanguageChooser.removeChooser(this._fieldId);
-               }
-       });
-       
-       return FormBuilderFieldContentLanguage;
-});
-
-/**
- * Abstract implementation of a handler for the visibility of container due the dependencies
- * of its children.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/Abstract
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/Abstract',['EventHandler', '../Manager'], function(EventHandler, DependencyManager) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function Abstract(containerId) {
-               this.init(containerId);
-       };
-       Abstract.prototype = {
-               /**
-                * Checks if the container should be visible and shows or hides it accordingly.
-                * 
-                * @abstract
-                */
-               checkContainer: function() {
-                       throw new Error("Missing implementation of WoltLabSuite/Core/Form/Builder/Field/Dependency/Container.checkContainer!");
-               },
-               
-               /**
-                * Initializes a new container dependency handler for the container with the given
-                * id.
-                * 
-                * @param       {string}        containerId     id of the handled container
-                * 
-                * @throws      {TypeError}                     if container id is no string
-                * @throws      {Error}                         if container id is invalid
-                */
-               init: function(containerId) {
-                       if (typeof containerId !== 'string') {
-                               throw new TypeError("Container id has to be a string.");
-                       }
-                       
-                       this._container = elById(containerId);
-                       if (this._container === null) {
-                               throw new Error("Unknown container with id '" + containerId + "'.");
-                       }
-                       
-                       DependencyManager.addContainerCheckCallback(this.checkContainer.bind(this));
-               }
-       };
-       
-       return Abstract
-});
-
-/**
- * Default implementation for a container visibility handler due to the dependencies of its
- * children that only considers the visibility of all of its children.
- *
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/Default
- * @see        module:WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/Default',['./Abstract', 'Core', '../Manager'], function(Abstract, Core, DependencyManager) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function Default(containerId) {
-               this.init(containerId);
-       };
-       Core.inherit(Default, Abstract, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/Default#checkContainer
-                */
-               checkContainer: function() {
-                       if (elDataBool(this._container, 'ignore-dependencies')) {
-                               return;
-                       }
-                       
-                       // only consider containers that have not been hidden by their own dependencies
-                       if (DependencyManager.isHiddenByDependencies(this._container)) {
-                               return;
-                       }
-                       
-                       var containerIsVisible = !elIsHidden(this._container);
-                       var containerShouldBeVisible = false;
-                       
-                       var children = this._container.children;
-                       var start = 0;
-                       // ignore container header for visibility considerations
-                       if (this._container.children.item(0).tagName === 'H2' || this._container.children.item(0).tagName === 'HEADER') {
-                               var start = 1;
-                       }
-                       
-                       for (var i = start, length = children.length; i < length; i++) {
-                               if (!elIsHidden(children.item(i))) {
-                                       containerShouldBeVisible = true;
-                                       break;
-                               }
-                       }
-                       
-                       if (containerIsVisible !== containerShouldBeVisible) {
-                               if (containerShouldBeVisible) {
-                                       elShow(this._container);
-                               }
-                               else {
-                                       elHide(this._container);
-                               }
-                               
-                               // check containers again to make sure parent containers can react to
-                               // changing the visibility of this container
-                               DependencyManager.checkContainers();
-                       }
-               }
-       });
-       
-       return Default;
-});
-
-/**
- * Container visibility handler implementation for a tab menu tab that, in addition to the
- * tab itself, also handles the visibility of the tab menu list item.
- *
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/Tab
- * @see        module:WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/Tab',['./Abstract', 'Core', 'Dom/Util', '../Manager', 'Ui/TabMenu'], function(Abstract, Core, DomUtil, DependencyManager, UiTabMenu) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function Tab(containerId) {
-               this.init(containerId);
-       };
-       Core.inherit(Tab, Abstract, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/Default#checkContainer
-                */
-               checkContainer: function() {
-                       // only consider containers that have not been hidden by their own dependencies
-                       if (DependencyManager.isHiddenByDependencies(this._container)) {
-                               return;
-                       }
-                       
-                       var containerIsVisible = !elIsHidden(this._container);
-                       var containerShouldBeVisible = false;
-                       
-                       var children = this._container.children;
-                       for (var i = 0, length = children.length; i < length; i++) {
-                               if (!elIsHidden(children.item(i))) {
-                                       containerShouldBeVisible = true;
-                                       break;
-                               }
-                       }
-                       
-                       if (containerIsVisible !== containerShouldBeVisible) {
-                               var tabMenuListItem = elBySel('#' + DomUtil.identify(this._container.parentNode) + ' > nav > ul > li[data-name=' + this._container.id + ']', this._container.parentNode.parentNode);
-                               if (tabMenuListItem === null) {
-                                       throw new Error("Cannot find tab menu entry for tab '" + this._container.id + "'.");
-                               }
-                               
-                               if (containerShouldBeVisible) {
-                                       elShow(this._container);
-                                       elShow(tabMenuListItem);
-                               }
-                               else {
-                                       elHide(this._container);
-                                       elHide(tabMenuListItem);
-                                       
-                                       var tabMenu = UiTabMenu.getTabMenu(DomUtil.identify(tabMenuListItem.closest('.tabMenuContainer')));
-                                       
-                                       // check if currently active tab will be hidden
-                                       if (tabMenu.getActiveTab() === tabMenuListItem) {
-                                               tabMenu.selectFirstVisible();
-                                       }
-                               }
-                               
-                               // check containers again to make sure parent containers can react to
-                               // changing the visibility of this container
-                               DependencyManager.checkContainers();
-                       }
-               }
-       });
-       
-       return Tab;
-});
-
-/**
- * Container visibility handler implementation for a tab menu that checks visibility
- * based on the visibility of its tab menu list items.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/TabMenu
- * @see        module:WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/TabMenu',['./Abstract', 'Core', 'Dom/Util', '../Manager', 'Ui/TabMenu'], function(Abstract, Core, DomUtil, DependencyManager, UiTabMenu) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function TabMenu(containerId) {
-               this.init(containerId);
-       };
-       Core.inherit(TabMenu, Abstract, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/Default#checkContainer
-                */
-               checkContainer: function() {
-                       // only consider containers that have not been hidden by their own dependencies
-                       if (DependencyManager.isHiddenByDependencies(this._container)) {
-                               return;
-                       }
-                       
-                       var containerIsVisible = !elIsHidden(this._container);
-                       var containerShouldBeVisible = false;
-                       
-                       var tabMenuListItems = elBySelAll('#' + DomUtil.identify(this._container) + ' > nav > ul > li', this._container.parentNode);
-                       for (var i = 0, length = tabMenuListItems.length; i < length; i++) {
-                               if (!elIsHidden(tabMenuListItems[i])) {
-                                       containerShouldBeVisible = true;
-                                       break;
-                               }
-                       }
-                       
-                       if (containerIsVisible !== containerShouldBeVisible) {
-                               if (containerShouldBeVisible) {
-                                       elShow(this._container);
-                                       
-                                       UiTabMenu.getTabMenu(DomUtil.identify(this._container)).selectFirstVisible();
-                               }
-                               else {
-                                       elHide(this._container);
-                               }
-                               
-                               // check containers again to make sure parent containers can react to
-                               // changing the visibility of this container
-                               DependencyManager.checkContainers();
-                       }
-               }
-       });
-       
-       return TabMenu;
-});
-
-/**
- * Abstract implementation of the JavaScript component of a form field handling
- * a list of packages.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList',['Dom/ChangeListener', 'Dom/Traverse', 'Dom/Util', 'EventKey', 'Language'], function(DomChangeListener, DomTraverse, DomUtil, EventKey, Language) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function AbstractPackageList(formFieldId, existingPackages) {
-               this.init(formFieldId, existingPackages);
-       };
-       AbstractPackageList.prototype = {
-               /**
-                * Initializes the package list handler.
-                * 
-                * @param       {string}        formFieldId             id of the associated form field
-                * @param       {object[]}      existingPackages        data of existing packages
-                */
-               init: function(formFieldId, existingPackages) {
-                       this._formFieldId = formFieldId;
-                       
-                       this._packageList = elById(this._formFieldId + '_packageList');
-                       if (this._packageList === null) {
-                               throw new Error("Cannot find package list for packages field with id '" + this._formFieldId + "'.");
-                       }
-                       
-                       this._packageIdentifier = elById(this._formFieldId + '_packageIdentifier');
-                       if (this._packageIdentifier === null) {
-                               throw new Error("Cannot find package identifier form field for packages field with id '" + this._formFieldId + "'.");
-                       }
-                       this._packageIdentifier.addEventListener('keypress', this._keyPress.bind(this));
-                       
-                       this._addButton = elById(this._formFieldId + '_addButton');
-                       if (this._addButton === null) {
-                               throw new Error("Cannot find add button for packages field with id '" + this._formFieldId + "'.");
-                       }
-                       this._addButton.addEventListener('click', this._addPackage.bind(this));
-                       
-                       this._form = this._packageList.closest('form');
-                       if (this._form === null) {
-                               throw new Error("Cannot find form element for packages field with id '" + this._formFieldId + "'.");
-                       }
-                       this._form.addEventListener('submit', this._submit.bind(this));
-                       
-                       existingPackages.forEach(this._addPackageByData.bind(this));
-               },
-               
-               /**
-                * Adds a package to the package list as a consequence of the given
-                * event. If the package data is invalid, an error message is shown
-                * and no package is added.
-                * 
-                * @param       {Event}         event   event that triggered trying to add the package
-                */
-               _addPackage: function(event) {
-                       event.preventDefault();
-                       event.stopPropagation();
-                       
-                       // validate data
-                       if (!this._validateInput()) {
-                               return;
-                       }
-                       
-                       this._addPackageByData(this._getInputData());
-                       
-                       // empty fields
-                       this._emptyInput();
-                       
-                       this._packageIdentifier.focus();
-               },
-               
-               /**
-                * Adds a package to the package list using the given package data.
-                * 
-                * @param       {object}        packageData
-                */
-               _addPackageByData: function(packageData) {
-                       // add package to list
-                       var listItem = elCreate('li');
-                       this._populateListItem(listItem, packageData);
-                       
-                       // add delete button
-                       var deleteButton = elCreate('span');
-                       deleteButton.className = 'icon icon16 fa-times pointer jsTooltip';
-                       elAttr(deleteButton, 'title', Language.get('wcf.global.button.delete'));
-                       deleteButton.addEventListener('click', this._removePackage.bind(this));
-                       DomUtil.prepend(deleteButton, listItem);
-                       
-                       this._packageList.appendChild(listItem);
-                       
-                       DomChangeListener.trigger();
-               },
-               
-               /**
-                * Creates the hidden fields when the form is submitted.
-                * 
-                * @param       {HTMLElement}   listElement     package list element from the package list
-                * @param       {int}           index           package index
-                */
-               _createSubmitFields: function(listElement, index) {
-                       var packageIdentifier = elCreate('input');
-                       elAttr(packageIdentifier, 'type', 'hidden');
-                       elAttr(packageIdentifier, 'name', this._formFieldId + '[' + index + '][packageIdentifier]')
-                       packageIdentifier.value = elData(listElement, 'package-identifier');
-                       this._form.appendChild(packageIdentifier);
-               },
-               
-               /**
-                * Empties the input fields.
-                */
-               _emptyInput() {
-                       this._packageIdentifier.value = '';
-               },
-               
-               /**
-                * Returns the error element for the given form field element.
-                * If `createIfNonExistent` is not given or `false`, `null` is returned
-                * if there is no error element, otherwise an empty error element
-                * is created and returned.
-                * 
-                * @param       {?boolean}      createIfNonExistent
-                * @return      {?HTMLElement}
-                */
-               _getErrorElement: function(element, createIfNoNExistent) {
-                       var error = DomTraverse.nextByClass(element, 'innerError');
-                       
-                       if (error === null && createIfNoNExistent) {
-                               error = elCreate('small');
-                               error.className = 'innerError';
-                               
-                               DomUtil.insertAfter(error, element);
-                       }
-                       
-                       return error;
-               },
-               
-               /**
-                * Returns the current data of the input fields to add a new package. 
-                * 
-                * @return      {object}
-                */
-               _getInputData: function() {
-                       return {
-                               packageIdentifier: this._packageIdentifier.value
-                       };
-               },
-               
-               /**
-                * Returns the error element for the package identifier form field.
-                * If `createIfNonExistent` is not given or `false`, `null` is returned
-                * if there is no error element, otherwise an empty error element
-                * is created and returned.
-                * 
-                * @param       {?boolean}      createIfNonExistent
-                * @return      {?HTMLElement}
-                */
-               _getPackageIdentifierErrorElement: function(createIfNonExistent) {
-                       return this._getErrorElement(this._packageIdentifier, createIfNonExistent);
-               },
-               
-               /**
-                * Adds a package to the package list after pressing ENTER in a
-                * text field.
-                * 
-                * @param       {Event}         event
-                */
-               _keyPress: function(event) {
-                       if (EventKey.Enter(event)) {
-                               this._addPackage(event);
-                       }
-               },
-               
-               /**
-                * Adds all necessary package-relavant data to the given list item.
-                * 
-                * @param       {HTMLElement}   listItem        package list element holding package data
-                * @param       {object}        packageData     package data
-                */
-               _populateListItem(listItem, packageData) {
-                       elData(listItem, 'package-identifier', packageData.packageIdentifier);
-               },
-               
-               /**
-                * Removes a package by clicking on its delete button.
-                * 
-                * @param       {Event}         event           delete button click event
-                */
-               _removePackage: function(event) {
-                       elRemove(event.currentTarget.closest('li'));
-                       
-                       // remove field errors if the last package has been deleted
-                       if (
-                               !this._packageList.childElementCount &&
-                               this._packageList.nextElementSibling.tagName === 'SMALL' &&
-                               this._packageList.nextElementSibling.classList.contains('innerError')
-                       ) {
-                               elRemove(this._packageList.nextElementSibling);
-                       }
-               },
-               
-               /**
-                * Adds all necessary (hidden) form fields to the form when
-                * submitting the form.
-                */
-               _submit: function() {
-                       DomTraverse.childrenByTag(this._packageList, 'LI').forEach(this._createSubmitFields.bind(this));
-               },
-               
-               /**
-                * Returns `true` if the currently entered package data is valid.
-                * Otherwise `false` is returned and relevant error messages are
-                * shown.
-                * 
-                * @return      {boolean}
-                */
-               _validateInput: function() {
-                       return this._validatePackageIdentifier();
-               },
-               
-               /**
-                * Returns `true` if the currently entered package identifier is
-                * valid. Otherwise `false` is returned and an error message is
-                * shown.
-                * 
-                * @return      {boolean}
-                */
-               _validatePackageIdentifier: function() {
-                       var packageIdentifier = this._packageIdentifier.value;
-                       
-                       if (packageIdentifier === '') {
-                               this._getPackageIdentifierErrorElement(true).textContent = Language.get('wcf.global.form.error.empty');
-                               
-                               return false;
-                       }
-                       
-                       if (packageIdentifier.length < 3) {
-                               this._getPackageIdentifierErrorElement(true).textContent = Language.get('wcf.acp.devtools.project.packageIdentifier.error.minimumLength');
-                               
-                               return false;
-                       }
-                       else if (packageIdentifier.length > 191) {
-                               this._getPackageIdentifierErrorElement(true).textContent = Language.get('wcf.acp.devtools.project.packageIdentifier.error.maximumLength');
-                               
-                               return false;
-                       }
-                       
-                       // see `wcf\data\package\Package::isValidPackageName()`
-                       if (!packageIdentifier.match(/^[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/)) {
-                               this._getPackageIdentifierErrorElement(true).textContent = Language.get('wcf.acp.devtools.project.packageIdentifier.error.format');
-                               
-                               return false;
-                       }
-                       
-                       // check if package has already been added
-                       var duplicate = false;
-                       DomTraverse.childrenByTag(this._packageList, 'LI').forEach(function(listItem, index) {
-                               if (elData(listItem, 'package-identifier') === packageIdentifier) {
-                                       duplicate = true;
-                               }
-                       });
-                       
-                       if (duplicate) {
-                               this._getPackageIdentifierErrorElement(true).textContent = Language.get('wcf.acp.devtools.project.packageIdentifier.error.duplicate');
-                               
-                               return false;
-                       }
-                       
-                       // remove outdated errors
-                       var error = this._getPackageIdentifierErrorElement();
-                       if (error !== null) {
-                               elRemove(error);
-                       }
-                       
-                       return true;
-               },
-               
-               /**
-                * Returns `true` if the given version is valid. Otherwise `false`
-                * is returned and an error message is shown.
-                * 
-                * @param       {string}        version                 validated version
-                * @param       {function}      versionErrorGetter      returns the version error element
-                * @return      {boolean}
-                */
-               _validateVersion: function(version, versionErrorGetter) {
-                       // see `wcf\data\package\Package::isValidVersion()`
-                       // the version is no a required attribute
-                       if (version !== '') {
-                               if (version.length > 255) {
-                                       versionErrorGetter(true).textContent = Language.get('wcf.acp.devtools.project.packageVersion.error.maximumLength');
-                                       
-                                       return false;
-                               }
-                               
-                               // see `wcf\data\package\Package::isValidVersion()`
-                               if (!version.match(/^([0-9]+)\.([0-9]+)\.([0-9]+)(\ (a|alpha|b|beta|d|dev|rc|pl)\ ([0-9]+))?$/i)) {
-                                       versionErrorGetter(true).textContent = Language.get('wcf.acp.devtools.project.packageVersion.error.format');
-                                       
-                                       return false;
-                               }
-                       }
-                       
-                       // remove outdated errors
-                       var error = versionErrorGetter();
-                       if (error !== null) {
-                               elRemove(error);
-                       }
-                       
-                       return true;
-               }
-       };
-       
-       return AbstractPackageList;
-});
-
-/**
- * Manages the packages entered in a devtools project excluded package form field.
- *
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/ExcludedPackages
- * @see        module:WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/ExcludedPackages',['./AbstractPackageList', 'Core', 'Language'], function(AbstractPackageList, Core, Language) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function ExcludedPackages(formFieldId, existingPackages) {
-               this.init(formFieldId, existingPackages);
-       };
-       Core.inherit(ExcludedPackages, AbstractPackageList, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList#init
-                */
-               init: function(formFieldId, existingPackages) {
-                       ExcludedPackages._super.prototype.init.call(this, formFieldId, existingPackages);
-                       
-                       this._version = elById(this._formFieldId + '_version');
-                       if (this._version === null) {
-                               throw new Error("Cannot find version form field for packages field with id '" + this._formFieldId + "'.");
-                       }
-                       this._version.addEventListener('keypress', this._keyPress.bind(this));
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList#_createSubmitFields
-                */
-               _createSubmitFields: function(listElement, index) {
-                       ExcludedPackages._super.prototype._createSubmitFields.call(this, listElement, index);
-                       
-                       var version = elCreate('input');
-                       elAttr(version, 'type', 'hidden');
-                       elAttr(version, 'name', this._formFieldId + '[' + index + '][version]')
-                       version.value = elData(listElement, 'version');
-                       this._form.appendChild(version);
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList#_emptyInput
-                */
-               _emptyInput() {
-                       ExcludedPackages._super.prototype._emptyInput.call(this);
-                       
-                       this._version.value = '';
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList#_getInputData
-                */
-               _getInputData: function() {
-                       return Core.extend(ExcludedPackages._super.prototype._getInputData.call(this), {
-                               version: this._version.value
-                       });
-               },
-               
-               /**
-                * Returns the error element for the version form field.
-                * If `createIfNonExistent` is not given or `false`, `null` is returned
-                * if there is no error element, otherwise an empty error element
-                * is created and returned.
-                *
-                * @param       {?boolean}      createIfNonExistent
-                * @return      {?HTMLElement}
-                */
-               _getVersionErrorElement: function(createIfNonExistent) {
-                       return this._getErrorElement(this._version, createIfNonExistent);
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList#_populateListItem
-                */
-               _populateListItem(listItem, packageData) {
-                       ExcludedPackages._super.prototype._populateListItem.call(this, listItem, packageData);
-                       
-                       elData(listItem, 'version', packageData.version);
-                       listItem.innerHTML = ' ' + Language.get('wcf.acp.devtools.project.excludedPackage.excludedPackage', {
-                               packageIdentifier: packageData.packageIdentifier,
-                               version: packageData.version
-                       });
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList#_validateInput
-                */
-               _validateInput: function() {
-                       return ExcludedPackages._super.prototype._validateInput.call(this) && this._validateVersion(
-                               this._version.value,
-                               this._getVersionErrorElement.bind(this)
-                       );
-               }
-       });
-       
-       return ExcludedPackages;
-});
-
-/**
- * Manages the instructions entered in a devtools project instructions form field. 
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/Instructions
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/Instructions',[
-       'Dom/ChangeListener',
-       'Dom/Traverse',
-       'Dom/Util',
-       'EventKey',
-       'Language',
-       'Ui/Confirmation',
-       'Ui/Dialog',
-       'WoltLabSuite/Core/Ui/Sortable/List'
-], function(
-       DomChangeListener,
-       DomTraverse,
-       DomUtil,
-       EventKey,
-       Language,
-       UiConfirmation,
-       UiDialog,
-       UiSortableList
-) {
-       "use strict";
-       
-       var _applicationPips = ['acpTemplate', 'file', 'script', 'template'];
-       
-       /**
-        * @constructor
-        */
-       function Instructions(
-               formFieldId,
-               instructionsTemplate,
-               instructionsEditDialogTemplate,
-               instructionEditDialogTemplate,
-               pipDefaultFilenames,
-               existingInstructions
-       ) {
-               this.init(
-                       formFieldId,
-                       instructionsTemplate,
-                       instructionsEditDialogTemplate,
-                       instructionEditDialogTemplate,
-                       pipDefaultFilenames,
-                       existingInstructions || []
-               );
-       };
-       Instructions.prototype = {
-               /**
-                * Initializes the instructions handler.
-                * 
-                * @param       {string}        formFieldId                     id of the associated form field
-                * @param       {Template}      instructionsTemplate            template used for a new set of instructions
-                * @param       {Template}      instructionsEditDialogTemplate  template used for instructions edit dialogs
-                * @param       {Template}      instructionEditDialogTemplate   template used for instruction edit dialogs
-                * @param       {object}        pipDefaultFilenames             maps pip names to their default filenames
-                * @param       {object[]}      existingInstructions            data of existing instructions
-                */
-               init: function(
-                       formFieldId,
-                       instructionsTemplate,
-                       instructionsEditDialogTemplate,
-                       instructionEditDialogTemplate,
-                       pipDefaultFilenames,
-                       existingInstructions
-               ) {
-                       this._formFieldId = formFieldId;
-                       this._instructionsTemplate = instructionsTemplate;
-                       this._instructionsEditDialogTemplate = instructionsEditDialogTemplate;
-                       this._instructionEditDialogTemplate = instructionEditDialogTemplate;
-                       this._instructionsCounter = 0;
-                       this._pipDefaultFilenames = pipDefaultFilenames;
-                       this._instructionCounter = 0;
-                       
-                       this._instructionsList = elById(this._formFieldId + '_instructionsList');
-                       if (this._instructionsList === null) {
-                               throw new Error("Cannot find package list for packages field with id '" + this._formFieldId + "'.");
-                       }
-                       
-                       this._instructionsType = elById(this._formFieldId + '_instructionsType');
-                       if (this._instructionsType === null) {
-                               throw new Error("Cannot find instruction type form field for instructions field with id '" + this._formFieldId + "'.");
-                       }
-                       this._instructionsType.addEventListener('change', this._toggleFromVersionFormField.bind(this));
-                       
-                       this._fromVersion = elById(this._formFieldId + '_fromVersion');
-                       if (this._fromVersion === null) {
-                               throw new Error("Cannot find from version form field for instructions field with id '" + this._formFieldId + "'.");
-                       }
-                       this._fromVersion.addEventListener('keypress', this._instructionsKeyPress.bind(this));
-                       
-                       this._addButton = elById(this._formFieldId + '_addButton');
-                       if (this._addButton === null) {
-                               throw new Error("Cannot find add button for instructions field with id '" + this._formFieldId + "'.");
-                       }
-                       this._addButton.addEventListener('click', this._addInstructions.bind(this));
-                       
-                       this._form = this._instructionsList.closest('form');
-                       if (this._form === null) {
-                               throw new Error("Cannot find form element for instructions field with id '" + this._formFieldId + "'.");
-                       }
-                       this._form.addEventListener('submit', this._submit.bind(this));
-                       
-                       var hasInstallInstructions = false;
-                       
-                       for (var index in existingInstructions) {
-                               var instructions = existingInstructions[index];
-                               
-                               if (instructions.type === 'install') {
-                                       hasInstallInstructions = true;
-                                       break;
-                               }
-                       }
-                       
-                       // ensure that there are always installation instructions
-                       if (!hasInstallInstructions) {
-                               this._addInstructionsByData({
-                                       fromVersion: '',
-                                       type: 'install'
-                               });
-                       }
-                       
-                       existingInstructions.forEach(this._addInstructionsByData.bind(this));
-                       
-                       DomChangeListener.trigger();
-               },
-               
-               /**
-                * Adds an instruction to a set of instructions as a consequence
-                * of the given event. If the instruction data is invalid, an
-                * error message is shown and no instruction is added.
-                * 
-                * @param       {Event}         event   event that triggered trying to add the instruction
-                */
-               _addInstruction: function(event) {
-                       event.preventDefault();
-                       event.stopPropagation();
-                       
-                       var instructionsId = elData(event.currentTarget.closest('li.section'), 'instructions-id');
-                       
-                       // note: data will be validated/filtered by the server
-                       
-                       var pipField = elById(this._formFieldId + '_instructions' + instructionsId + '_pip');
-                       
-                       // ignore pressing button if no PIP has been selected
-                       if (!pipField.value) {
-                               return;
-                       }
-                       
-                       var valueField = elById(this._formFieldId + '_instructions' + instructionsId + '_value');
-                       var runStandaloneField = elById(this._formFieldId + '_instructions' + instructionsId + '_runStandalone');
-                       var applicationField = elById(this._formFieldId + '_instructions' + instructionsId + '_application');
-                       
-                       this._addInstructionByData(instructionsId, {
-                               application: _applicationPips.indexOf(pipField.value) !== -1 ? applicationField.value : '',
-                               pip: pipField.value,
-                               runStandalone: ~~runStandaloneField.checked,
-                               value: valueField.value
-                       });
-                       
-                       // empty fields
-                       pipField.value = '';
-                       valueField.value = '';
-                       runStandaloneField.checked = false;
-                       applicationField.value = '';
-                       elById(this._formFieldId + '_instructions' + instructionsId + '_valueDescription').innerHTML = Language.get('wcf.acp.devtools.project.instruction.value.description');
-                       this._toggleApplicationFormField(instructionsId);
-                       
-                       DomChangeListener.trigger();
-               },
-               
-               /**
-                * Adds an instruction to the set of instructions with the given id.
-                * 
-                * @param       {int}           instructionsId
-                * @param       {object}        instructionData
-                */
-               _addInstructionByData: function(instructionsId, instructionData) {
-                       var instructionId = ++this._instructionCounter;
-                       
-                       var instructionList = elById(this._formFieldId + '_instructions' + instructionsId + '_instructionList');
-                       
-                       var listItem = elCreate('li');
-                       listItem.className = 'sortableNode';
-                       listItem.id = this._formFieldId + '_instruction' + instructionId;
-                       elData(listItem, 'instruction-id', instructionId);
-                       elData(listItem, 'application', instructionData.application);
-                       elData(listItem, 'pip', instructionData.pip);
-                       elData(listItem, 'runStandalone', instructionData.runStandalone);
-                       elData(listItem, 'value', instructionData.value);
-                       
-                       var content = '' +
-                               '<div class="sortableNodeLabel">' +
-                               '       <div class="jsDevtoolsProjectInstruction">' +
-                               '               ' + Language.get('wcf.acp.devtools.project.instruction.instruction', instructionData);
-                       
-                       if (instructionData.errors) {
-                               for (var index in instructionData.errors) {
-                                       content += '<small class="innerError">' + instructionData.errors[index] + '</small>';
-                               }
-                       }
-                       
-                       content += '' +
-                                       '       </div>' +
-                               '       <span class="statusDisplay sortableButtonContainer">' +
-                               '               <span class="icon icon16 fa-pencil pointer jsTooltip" id="' + this._formFieldId + '_instruction' + instructionId + '_editButton" title="' + Language.get('wcf.global.button.edit') + '"></span>' +
-                               '               <span class="icon icon16 fa-times pointer jsTooltip" id="' + this._formFieldId + '_instruction' + instructionId + '_deleteButton" title="' + Language.get('wcf.global.button.delete') + '"></span>' +
-                               '       </span>' +
-                               '</div>';
-                       
-                       listItem.innerHTML = content;
-                       
-                       instructionList.appendChild(listItem);
-                       
-                       elById(this._formFieldId + '_instruction' + instructionId + '_deleteButton').addEventListener('click', this._removeInstruction.bind(this));
-                       elById(this._formFieldId + '_instruction' + instructionId + '_editButton').addEventListener('click', this._editInstruction.bind(this));
-               },
-               
-               /**
-                * Adds a set of instructions as a consequenc of the given event.
-                * If the instructions data is invalid, an error message is shown
-                * and no instruction set is added.
-                * 
-                * @param       {Event}         event   event that triggered trying to add the instructions
-                */
-               _addInstructions: function(event) {
-                       event.preventDefault();
-                       event.stopPropagation();
-                       
-                       // validate data
-                       if (!this._validateInstructionsType() || (this._instructionsType.value === 'update' && !this._validateFromVersion(this._fromVersion))) {
-                               return;
-                       }
-                       
-                       this._addInstructionsByData({
-                               fromVersion: this._instructionsType.value === 'update' ? this._fromVersion.value : '',
-                               type: this._instructionsType.value
-                       });
-                       
-                       // empty fields
-                       this._instructionsType.value = '';
-                       this._fromVersion.value = '';
-                       
-                       this._toggleFromVersionFormField();
-                       
-                       DomChangeListener.trigger();
-               },
-               
-               /**
-                * Adds a set of instructions.
-                * 
-                * @param       {object}        instructionData
-                */
-               _addInstructionsByData: function(instructionsData) {
-                       var instructionsId = ++this._instructionsCounter;
-                       
-                       var listItem = elCreate('li');
-                       listItem.className = 'section';
-                       listItem.innerHTML = this._instructionsTemplate.fetch({
-                               instructionsId: instructionsId,
-                               sectionTitle: Language.get('wcf.acp.devtools.project.instructions.type.' + instructionsData.type + '.title', {
-                                       fromVersion: instructionsData.fromVersion
-                               }),
-                               type: instructionsData.type
-                       });
-                       
-                       listItem.id = this._formFieldId + '_instructions' + instructionsId;
-                       elData(listItem, 'instructions-id', instructionsId);
-                       elData(listItem, 'type', instructionsData.type);
-                       elData(listItem, 'fromVersion', instructionsData.fromVersion);
-                       
-                       elById(this._formFieldId + '_instructions' + instructionsId + '_valueDescription')
-                       
-                       this._instructionsList.appendChild(listItem);
-                       
-                       var instructionListContainer = elById(this._formFieldId + '_instructions' + instructionsId + '_instructionListContainer');
-                       for (var errorMessage of instructionsData.errors || []) {
-                               var small = elCreate('small');
-                               small.className = 'innerError';
-                               small.innerHTML = errorMessage;
-                               
-                               instructionListContainer.parentNode.insertBefore(small, instructionListContainer);
-                       }
-                       
-                       new UiSortableList({
-                               containerId: instructionListContainer.id,
-                               isSimpleSorting: true,
-                               options: {
-                                       toleranceElement: '> div'
-                               }
-                       });
-                       
-                       var deleteButton = elById(this._formFieldId + '_instructions' + instructionsId + '_deleteButton');
-                       if (instructionsData.type === 'update') {
-                               elById(this._formFieldId + '_instructions' + instructionsId + '_deleteButton').addEventListener('click', this._removeInstructions.bind(this));
-                               elById(this._formFieldId + '_instructions' + instructionsId + '_editButton').addEventListener('click', this._editInstructions.bind(this));
-                       }
-                       
-                       elById(this._formFieldId + '_instructions' + instructionsId + '_pip').addEventListener('change', this._changeInstructionPip.bind(this));
-                       elById(this._formFieldId + '_instructions' + instructionsId + '_value').addEventListener('keypress', this._instructionKeyPress.bind(this));
-                       elById(this._formFieldId + '_instructions' + instructionsId + '_addButton').addEventListener('click', this._addInstruction.bind(this));
-                       
-                       if (instructionsData.instructions) {
-                               for (var index in instructionsData.instructions) {
-                                       this._addInstructionByData(instructionsId, instructionsData.instructions[index]);
-                               }
-                       }
-               },
-               
-               /**
-                * Is called if the selected package installation plugin of an
-                * instruction is changed.
-                * 
-                * @param       {Event}         event           change event
-                */
-               _changeInstructionPip: function(event) {
-                       var pip = event.currentTarget.value;
-                       var instructionsId = elData(event.currentTarget.closest('li.section'), 'instructions-id');
-                       var description = elById(this._formFieldId + '_instructions' + instructionsId + '_valueDescription');
-                       
-                       // update value description
-                       if (this._pipDefaultFilenames[pip] !== '') {
-                               description.innerHTML = Language.get('wcf.acp.devtools.project.instruction.value.description.defaultFilename', {
-                                       defaultFilename: this._pipDefaultFilenames[pip]
-                               });
-                       }
-                       else {
-                               description.innerHTML = Language.get('wcf.acp.devtools.project.instruction.value.description');
-                       }
-                       
-                       var valueDlClassList = elById(this._formFieldId + '_instructions' + instructionsId + '_value').closest('dl').classList;
-                       var applicationDl = elById(this._formFieldId + '_instructions' + instructionsId + '_application').closest('dl');
-                       
-                       // toggle application selector
-                       this._toggleApplicationFormField(instructionsId);
-               },
-               
-               /**
-                * Opens a dialog to edit an existing instruction.
-                * 
-                * @param       {Event}         event   edit button click event
-                */
-               _editInstruction: function(event) {
-                       var listItem = event.currentTarget.closest('li');
-                       
-                       var instructionId = elData(listItem, 'instruction-id');
-                       var application = elData(listItem, 'application');
-                       var pip = elData(listItem, 'pip');
-                       var runStandalone = elDataBool(listItem, 'runStandalone');
-                       var value = elData(listItem, 'value');
-                       
-                       var dialogContent = this._instructionEditDialogTemplate.fetch({
-                               runStandalone: runStandalone,
-                               value: value
-                       });
-                       
-                       var dialogId = 'instructionEditDialog' + instructionId;
-                       if (!UiDialog.getDialog(dialogId)) {
-                               UiDialog.openStatic(dialogId, dialogContent, {
-                                       onSetup: function(content) {
-                                               var applicationSelect = elBySel('select[name=application]', content);
-                                               var pipSelect = elBySel('select[name=pip]', content);
-                                               var runStandaloneInput = elBySel('input[name=runStandalone]', content);
-                                               var valueInput = elBySel('input[name=value]', content);
-                                               
-                                               // set values of `select` elements
-                                               applicationSelect.value = application;
-                                               pipSelect.value = pip;
-                                               
-                                               var submit = function() {
-                                                       var listItem = elById(this._formFieldId + '_instruction' + instructionId);
-                                                       elData(listItem, 'application', _applicationPips.indexOf(pipSelect.value) !== -1 ? applicationSelect.value : '');
-                                                       elData(listItem, 'pip', pipSelect.value);
-                                                       elData(listItem, 'runStandalone', ~~runStandaloneInput.checked);
-                                                       elData(listItem, 'value', valueInput.value);
-                                                       
-                                                       // note: data will be validated/filtered by the server
-                                                       
-                                                       elByClass('jsDevtoolsProjectInstruction', listItem)[0].innerHTML = Language.get('wcf.acp.devtools.project.instruction.instruction', {
-                                                               application: elData(listItem, 'application'),
-                                                               pip: elData(listItem, 'pip'),
-                                                               runStandalone: elDataBool(listItem, 'runStandalone'),
-                                                               value: elData(listItem, 'value'),
-                                                       });
-                                                       
-                                                       DomChangeListener.trigger();
-                                                       
-                                                       UiDialog.close(dialogId);
-                                               }.bind(this);
-                                               
-                                               valueInput.addEventListener('keypress', function(event) {
-                                                       if (EventKey.Enter(event)) {
-                                                               submit();
-                                                       }
-                                               });
-                                               
-                                               elBySel('button[data-type=submit]', content).addEventListener('click', submit);
-                                               
-                                               var pipChange = function() {
-                                                       var pip = pipSelect.value;
-                                                       
-                                                       if (_applicationPips.indexOf(pip) !== -1) {
-                                                               elShow(applicationSelect.closest('dl'));
-                                                       }
-                                                       else {
-                                                               elHide(applicationSelect.closest('dl'));
-                                                       }
-                                                       
-                                                       var description = DomTraverse.nextByTag(valueInput, 'SMALL');
-                                                       if (this._pipDefaultFilenames[pip] !== '') {
-                                                               description.innerHTML = Language.get('wcf.acp.devtools.project.instruction.value.description.defaultFilename', {
-                                                                       defaultFilename: this._pipDefaultFilenames[pip]
-                                                               });
-                                                       }
-                                                       else {
-                                                               description.innerHTML = Language.get('wcf.acp.devtools.project.instruction.value.description');
-                                                       }
-                                               }.bind(this);
-                                               
-                                               pipSelect.addEventListener('change', pipChange);
-                                               pipChange();
-                                       }.bind(this),
-                                       title: Language.get('wcf.acp.devtools.project.instruction.edit')
-                               });
-                       }
-                       else {
-                               UiDialog.openStatic(dialogId);
-                       }
-               },
-               
-               /**
-                * Opens a dialog to edit an existing set of instructions.
-                * 
-                * @param       {Event}         event   edit button click event
-                */
-               _editInstructions: function(event) {
-                       var listItem = event.currentTarget.closest('li');
-                       
-                       var instructionsId = elData(listItem, 'instructions-id');
-                       var fromVersion = elData(listItem, 'fromVersion');
-                       
-                       var dialogContent = this._instructionsEditDialogTemplate.fetch({
-                               fromVersion: fromVersion
-                       });
-                       
-                       var dialogId = 'instructionsEditDialog' + instructionsId;
-                       if (!UiDialog.getDialog(dialogId)) {
-                               UiDialog.openStatic(dialogId, dialogContent, {
-                                       onSetup: function (content) {
-                                               var fromVersion = elBySel('input[name=fromVersion]', content);
-                                               
-                                               var submit = function () {
-                                                       if (!this._validateFromVersion(fromVersion)) {
-                                                               return;
-                                                       }
-                                                       
-                                                       var instructions = elById(this._formFieldId + '_instructions' + instructionsId);
-                                                       elData(instructions, 'fromVersion', fromVersion.value);
-                                                       
-                                                       elByClass('jsInstructionsTitle', instructions)[0].textContent = Language.get('wcf.acp.devtools.project.instructions.type.update.title', {
-                                                               fromVersion: fromVersion.value
-                                                       });
-                                                       
-                                                       DomChangeListener.trigger();
-                                                       
-                                                       UiDialog.close(dialogId);
-                                               }.bind(this);
-                                               
-                                               fromVersion.addEventListener('keypress', function (event) {
-                                                       if (EventKey.Enter(event)) {
-                                                               submit();
-                                                       }
-                                               });
-                                               
-                                               elBySel('button[data-type=submit]', content).addEventListener('click', submit);
-                                       }.bind(this),
-                                       title: Language.get('wcf.acp.devtools.project.instructions.edit')
-                               });
-                       }
-                       else {
-                               UiDialog.openStatic(dialogId);
-                       }
-               },
-               
-               /**
-                * Returns the error element for the given form field element.
-                * If `createIfNonExistent` is not given or `false`, `null` is returned
-                * if there is no error element, otherwise an empty error element
-                * is created and returned.
-                *
-                * @param       {?boolean}      createIfNonExistent
-                * @return      {?HTMLElement}
-                */
-               _getErrorElement: function(element, createIfNoNExistent) {
-                       var error = DomTraverse.nextByClass(element, 'innerError');
-                       
-                       if (error === null && createIfNoNExistent) {
-                               error = elCreate('small');
-                               error.className = 'innerError';
-                               
-                               DomUtil.insertAfter(error, element);
-                       }
-                       
-                       return error;
-               },
-               
-               /**
-                * Returns the error element for the from version form field.
-                * If `createIfNonExistent` is not given or `false`, `null` is returned
-                * if there is no error element, otherwise an empty error element
-                * is created and returned.
-                *
-                * @param       {?boolean}      createIfNonExistent
-                * @return      {?HTMLElement}
-                */
-               _getFromVersionErrorElement: function(inputField, createIfNonExistent) {
-                       return this._getErrorElement(inputField, createIfNonExistent);
-               },
-               
-               /**
-                * Returns the error element for the instruction type form field.
-                * If `createIfNonExistent` is not given or `false`, `null` is returned
-                * if there is no error element, otherwise an empty error element
-                * is created and returned.
-                *
-                * @param       {?boolean}      createIfNonExistent
-                * @return      {?HTMLElement}
-                */
-               _getInstructionsTypeErrorElement: function(createIfNonExistent) {
-                       return this._getErrorElement(this._instructionsType, createIfNonExistent);
-               },
-               
-               /**
-                * Adds an instruction after pressing ENTER in a relevant text
-                * field.
-                * 
-                * @param       {Event}         event
-                */
-               _instructionKeyPress: function(event) {
-                       if (EventKey.Enter(event)) {
-                               this._addInstruction(event);
-                       }
-               },
-               
-               /**
-                * Adds a set of instruction after pressing ENTER in a relevant
-                * text field.
-                * 
-                * @param       {Event}         event
-                */
-               _instructionsKeyPress: function(event) {
-                       if (EventKey.Enter(event)) {
-                               this._addInstructions(event);
-                       }
-               },
-               
-               /**
-                * Removes an instruction by clicking on its delete button.
-                * 
-                * @param       {Event}         event           delete button click event
-                */
-               _removeInstruction: function(event) {
-                       var instruction = event.currentTarget.closest('li');
-                       
-                       UiConfirmation.show({
-                               confirm: function() {
-                                       elRemove(instruction);
-                               },
-                               message: Language.get('wcf.acp.devtools.project.instruction.delete.confirmMessages')
-                       });
-               },
-               
-               /**
-                * Removes a set of instructions by clicking on its delete button.
-                * 
-                * @param       {Event}         event           delete button click event
-                */
-               _removeInstructions: function(event) {
-                       var instructions = event.currentTarget.closest('li');
-                       
-                       UiConfirmation.show({
-                               confirm: function() {
-                                       elRemove(instructions);
-                               },
-                               message: Language.get('wcf.acp.devtools.project.instructions.delete.confirmMessages')
-                       });
-               },
-               
-               /**
-                * Adds all necessary (hidden) form fields to the form when
-                * submitting the form.
-                */
-               _submit: function(event) {
-                       DomTraverse.childrenByTag(this._instructionsList, 'LI').forEach(function(instructions, instructionsIndex) {
-                               var namePrefix = this._formFieldId + '[' + instructionsIndex + ']';
-                               
-                               var instructionsType = elCreate('input');
-                               elAttr(instructionsType, 'type', 'hidden');
-                               elAttr(instructionsType, 'name', namePrefix + '[type]')
-                               instructionsType.value = elData(instructions, 'type');
-                               this._form.appendChild(instructionsType);
-                               
-                               if (instructionsType.value === 'update') {
-                                       var fromVersion = elCreate('input');
-                                       elAttr(fromVersion, 'type', 'hidden');
-                                       elAttr(fromVersion, 'name', this._formFieldId + '[' + instructionsIndex + '][fromVersion]')
-                                       fromVersion.value = elData(instructions, 'fromVersion');
-                                       this._form.appendChild(fromVersion);
-                               }
-                               
-                               DomTraverse.childrenByTag(elById(instructions.id + '_instructionList'), 'LI').forEach(function(instruction, instructionIndex) {
-                                       var namePrefix = this._formFieldId + '[' + instructionsIndex + '][instructions][' + instructionIndex + ']';
-                                       
-                                       for (var property of ['pip', 'value', 'runStandalone']) {
-                                               var element = elCreate('input');
-                                               elAttr(element, 'type', 'hidden');
-                                               elAttr(element, 'name', namePrefix + '[' + property + ']')
-                                               element.value = elData(instruction, property);
-                                               this._form.appendChild(element);
-                                       }
-                                       
-                                       if (_applicationPips.indexOf(elData(instruction, 'pip')) !== -1) {
-                                               var application = elCreate('input');
-                                               elAttr(application, 'type', 'hidden');
-                                               elAttr(application, 'name', namePrefix + '[application]')
-                                               application.value = elData(instruction, 'application');
-                                               this._form.appendChild(application);
-                                       }
-                               }.bind(this));
-                       }.bind(this));
-               },
-               
-               /**
-                * Toggles the visibility of the application form field based on
-                * the selected pip for the instructions with the given id.
-                *
-                * @param       {int}   instructionsId          id of the relevant instruction set
-                */
-               _toggleApplicationFormField: function(instructionsId) {
-                       var pip = elById(this._formFieldId + '_instructions' + instructionsId + '_pip').value;
-                       
-                       var valueDlClassList = elById(this._formFieldId + '_instructions' + instructionsId + '_value').closest('dl').classList;
-                       var applicationDl = elById(this._formFieldId + '_instructions' + instructionsId + '_application').closest('dl');
-                       
-                       if (_applicationPips.indexOf(pip) !== -1) {
-                               valueDlClassList.remove('col-md-9');
-                               valueDlClassList.add('col-md-7');
-                               elShow(applicationDl);
-                       }
-                       else {
-                               valueDlClassList.remove('col-md-7');
-                               valueDlClassList.add('col-md-9');
-                               elHide(applicationDl);
-                       }
-               },
-               
-               /**
-                * Toggles the visibility of the `fromVersion` form field based on
-                * the selected instructions type.
-                */
-               _toggleFromVersionFormField: function() {
-                       var instructionsTypeList = this._instructionsType.closest('dl').classList;
-                       var fromVersionDl = this._fromVersion.closest('dl');
-                       
-                       if (this._instructionsType.value === 'update') {
-                               instructionsTypeList.remove('col-md-10');
-                               instructionsTypeList.add('col-md-5');
-                               elShow(fromVersionDl);
-                       }
-                       else {
-                               instructionsTypeList.remove('col-md-5');
-                               instructionsTypeList.add('col-md-10');
-                               elHide(fromVersionDl);
-                       }
-               },
-               
-               /**
-                * Returns `true` if the currently entered update "from version"
-                * is valid. Otherwise `false` is returned and an error message
-                * is shown.
-                * 
-                * @return      {boolean}
-                */
-               _validateFromVersion: function(inputField) {
-                       var version = inputField.value;
-                       
-                       if (version === '') {
-                               this._getFromVersionErrorElement(inputField, true).textContent = Language.get('wcf.global.form.error.empty');
-                               
-                               return false;
-                       }
-                       
-                       if (version.length > 50) {
-                               this._getFromVersionErrorElement(inputField, true).textContent = Language.get('wcf.acp.devtools.project.packageVersion.error.maximumLength');
-                               
-                               return false;
-                       }
-                       
-                       // wildcard versions are checked on the server side
-                       if (version.indexOf('*') === -1) {
-                               // see `wcf\data\package\Package::isValidVersion()`
-                               if (!version.match(/^([0-9]+)\.([0-9]+)\.([0-9]+)(\ (a|alpha|b|beta|d|dev|rc|pl)\ ([0-9]+))?$/i)) {
-                                       this._getFromVersionErrorElement(inputField, true).textContent = Language.get('wcf.acp.devtools.project.packageVersion.error.format');
-                                       
-                                       return false;
-                               }
-                       }
-                       else if (!version.replace('*', '0').match(/^([0-9]+)\.([0-9]+)\.([0-9]+)(\ (a|alpha|b|beta|d|dev|rc|pl)\ ([0-9]+))?$/i)) {
-                               this._getFromVersionErrorElement(inputField, true).textContent = Language.get('wcf.acp.devtools.project.packageVersion.error.format');
-                               
-                               return false;
-                       }
-                       
-                       // remove outdated errors
-                       var error = this._getFromVersionErrorElement(inputField);
-                       if (error !== null) {
-                               elRemove(error);
-                       }
-                       
-                       return true;
-               },
-               
-               /**
-                * Returns `true` if the entered update instructions type is valid.
-                * Otherwise `false` is returned and an error message is shown.
-                * 
-                * @return      {boolean}
-                */
-               _validateInstructionsType: function() {
-                       if (this._instructionsType.value !== 'install' && this._instructionsType.value !== 'update') {
-                               if (this._instructionsType.value === '') {
-                                       this._getInstructionsTypeErrorElement(true).textContent = Language.get('wcf.global.form.error.empty');
-                               }
-                               else {
-                                       this._getInstructionsTypeErrorElement(true).textContent = Language.get('wcf.global.form.error.noValidSelection');
-                               }
-                               
-                               return false;
-                       }
-                       
-                       // there may only be one set of installation instructions 
-                       if (this._instructionsType.value === 'install') {
-                               var hasInstall = false;
-                               [].forEach.call(this._instructionsList.children, function(instructions) {
-                                       if (elData(instructions, 'type') === 'install') {
-                                               hasInstall = true;
-                                       }
-                               });
-                               
-                               if (hasInstall) {
-                                       this._getInstructionsTypeErrorElement(true).textContent = Language.get('wcf.acp.devtools.project.instructions.type.update.error.duplicate');
-                                       
-                                       return false;
-                               }
-                       }
-                       
-                       // remove outdated errors
-                       var error = this._getInstructionsTypeErrorElement();
-                       if (error !== null) {
-                               elRemove(error);
-                       }
-                       
-                       return true;
-               }
-       };
-       
-       return Instructions;
-});
-
-/**
- * Manages the packages entered in a devtools project optional package form field.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/OptionalPackages
- * @see        module:WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/OptionalPackages',['./AbstractPackageList', 'Core', 'Language'], function(AbstractPackageList, Core, Language) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function OptionalPackages(formFieldId, existingPackages) {
-               this.init(formFieldId, existingPackages);
-       };
-       Core.inherit(OptionalPackages, AbstractPackageList, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList#_populateListItem
-                */
-               _populateListItem(listItem, packageData) {
-                       OptionalPackages._super.prototype._populateListItem.call(this, listItem, packageData);
-                       
-                       listItem.innerHTML = ' ' + Language.get('wcf.acp.devtools.project.optionalPackage.optionalPackage', {
-                               file: packageData.file,
-                               packageIdentifier: packageData.packageIdentifier
-                       });
-               }
-       });
-       
-       return OptionalPackages;
-});
-
-/**
- * Manages the packages entered in a devtools project required package form field.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/RequiredPackages
- * @see        module:WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/RequiredPackages',['./AbstractPackageList', 'Core', 'Language'], function(AbstractPackageList, Core, Language) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function RequiredPackages(formFieldId, existingPackages) {
-               this.init(formFieldId, existingPackages);
-       };
-       Core.inherit(RequiredPackages, AbstractPackageList, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList#init
-                */
-               init: function(formFieldId, existingPackages) {
-                       RequiredPackages._super.prototype.init.call(this, formFieldId, existingPackages);
-                       
-                       this._minVersion = elById(this._formFieldId + '_minVersion');
-                       if (this._minVersion === null) {
-                               throw new Error("Cannot find minimum version form field for packages field with id '" + this._formFieldId + "'.");
-                       }
-                       this._minVersion.addEventListener('keypress', this._keyPress.bind(this));
-                       
-                       this._file = elById(this._formFieldId + '_file');
-                       if (this._file === null) {
-                               throw new Error("Cannot find file form field for required field with id '" + this._formFieldId + "'.");
-                       }
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList#_createSubmitFields
-                */
-               _createSubmitFields: function(listElement, index) {
-                       RequiredPackages._super.prototype._createSubmitFields.call(this, listElement, index);
-                       
-                       var minVersion = elCreate('input');
-                       elAttr(minVersion, 'type', 'hidden');
-                       elAttr(minVersion, 'name', this._formFieldId + '[' + index + '][minVersion]')
-                       minVersion.value = elData(listElement, 'min-version');
-                       this._form.appendChild(minVersion);
-                       
-                       var file = elCreate('input');
-                       elAttr(file, 'type', 'hidden');
-                       elAttr(file, 'name', this._formFieldId + '[' + index + '][file]')
-                       file.value = elData(listElement, 'file');
-                       this._form.appendChild(file);
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList#_emptyInput
-                */
-               _emptyInput() {
-                       RequiredPackages._super.prototype._emptyInput.call(this);
-                       
-                       this._minVersion.value = '';
-                       this._file.checked = false;
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList#_getInputData
-                */
-               _getInputData: function() {
-                       return Core.extend(RequiredPackages._super.prototype._getInputData.call(this), {
-                               file: this._file.checked,
-                               minVersion: this._minVersion.value
-                       });
-               },
-               
-               /**
-                * Returns the error element for the minimum version form field.
-                * If `createIfNonExistent` is not given or `false`, `null` is returned
-                * if there is no error element, otherwise an empty error element
-                * is created and returned.
-                *
-                * @param       {?boolean}      createIfNonExistent
-                * @return      {?HTMLElement}
-                */
-               _getMinVersionErrorElement: function(createIfNonExistent) {
-                       return this._getErrorElement(this._minVersion, createIfNonExistent);
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList#_populateListItem
-                */
-               _populateListItem(listItem, packageData) {
-                       RequiredPackages._super.prototype._populateListItem.call(this, listItem, packageData);
-                       
-                       elData(listItem, 'min-version', packageData.minVersion);
-                       elData(listItem, 'file', ~~packageData.file);
-                       listItem.innerHTML = ' ' + Language.get('wcf.acp.devtools.project.requiredPackage.requiredPackage', {
-                               file: ~~packageData.file,
-                               minVersion: packageData.minVersion,
-                               packageIdentifier: packageData.packageIdentifier
-                       });
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList#_validateInput
-                */
-               _validateInput: function() {
-                       return RequiredPackages._super.prototype._validateInput.call(this) && this._validateVersion(
-                               this._minVersion.value,
-                               this._getMinVersionErrorElement.bind(this)
-                       );
-               }
-       });
-       
-       return RequiredPackages;
-});
-
-/**
- * Default implementation for user interaction menu items used in the user profile.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/User/Profile/Menu/Item/Abstract
- */
-define('WoltLabSuite/Core/Ui/User/Profile/Menu/Item/Abstract',['Ajax', 'Dom/Util'], function(Ajax, DomUtil) {
-       "use strict";
-       
-       /**
-        * Creates a new user profile menu item.
-        * 
-        * @param       {int}           userId          user id
-        * @param       {boolean}       isActive        true if item is initially active
-        * @constructor
-        */
-       function UiUserProfileMenuItemAbstract(userId, isActive) {}
-       UiUserProfileMenuItemAbstract.prototype = {
-               /**
-                * Creates a new user profile menu item.
-                * 
-                * @param       {int}           userId          user id
-                * @param       {boolean}       isActive        true if item is initially active
-                */
-               init: function(userId, isActive) {
-                       this._userId = userId;
-                       this._isActive = (isActive !== false);
-                       
-                       this._initButton();
-                       this._updateButton();
-               },
-               
-               /**
-                * Initializes the menu item.
-                * 
-                * @protected
-                */
-               _initButton: function() {
-                       var button = elCreate('a');
-                       button.href = '#';
-                       button.addEventListener(WCF_CLICK_EVENT, this._toggle.bind(this));
-                       
-                       var listItem = elCreate('li');
-                       listItem.appendChild(button);
-                       
-                       var menu = elBySel('.userProfileButtonMenu[data-menu="interaction"]');
-                       DomUtil.prepend(listItem, menu);
-                       
-                       this._button = button;
-                       this._listItem = listItem;
-               },
-               
-               /**
-                * Handles clicks on the menu item button.
-                * 
-                * @param       {Event}         event   event object
-                * @protected
-                */
-               _toggle: function(event) {
-                       event.preventDefault();
-                       
-                       Ajax.api(this, {
-                               actionName: this._getAjaxActionName(),
-                               parameters: {
-                                       data: {
-                                               userID: this._userId
-                                       }
-                               }
-                       });
-               },
-               
-               /**
-                * Updates the button state and label.
-                * 
-                * @protected
-                */
-               _updateButton: function() {
-                       this._button.textContent = this._getLabel();
-                       this._listItem.classList[(this._isActive ? 'add' : 'remove')]('active');
-               },
-               
-               /**
-                * Returns the button label.
-                * 
-                * @return      {string}        button label
-                * @protected
-                * @abstract
-                */
-               _getLabel: function() {
-                       throw new Error("Implement me!");
-               },
-               
-               /**
-                * Returns the Ajax action name.
-                * 
-                * @return      {string}        ajax action name
-                * @protected
-                * @abstract
-                */
-               _getAjaxActionName: function() {
-                       throw new Error("Implement me!");
-               },
-               
-               /**
-                * Handles successful Ajax requests.
-                * 
-                * @protected
-                * @abstract
-                */
-               _ajaxSuccess: function() {
-                       throw new Error("Implement me!");
-               },
-               
-               /**
-                * Returns the default Ajax request data
-                * 
-                * @return      {Object}        ajax request data
-                * @protected
-                * @abstract
-                */
-               _ajaxSetup: function() {
-                       throw new Error("Implement me!");
-               }
-       };
-       
-       return UiUserProfileMenuItemAbstract;
-});
-
-define('WoltLabSuite/Core/Ui/User/Profile/Menu/Item/Follow',['Core', 'Language', 'Ui/Notification', './Abstract'], function(Core, Language, UiNotification, UiUserProfileMenuItemAbstract) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       _getLabel: function() {},
-                       _getAjaxActionName: function() {},
-                       _ajaxSuccess: function() {},
-                       _ajaxSetup: function() {},
-                       init: function() {},
-                       _initButton: function() {},
-                       _toggle: function() {},
-                       _updateButton: function() {}
-               };
-               return Fake;
-       }
-       
-       function UiUserProfileMenuItemFollow(userId, isActive) { this.init(userId, isActive); }
-       Core.inherit(UiUserProfileMenuItemFollow, UiUserProfileMenuItemAbstract, {
-               _getLabel: function() {
-                       return Language.get('wcf.user.button.' + (this._isActive ? 'un' : '') + 'follow');
-               },
-               
-               _getAjaxActionName: function() {
-                       return this._isActive ? 'unfollow' : 'follow';
-               },
-               
-               _ajaxSuccess: function(data) {
-                       this._isActive = (data.returnValues.following ? true : false);
-                       this._updateButton();
-                       
-                       UiNotification.show();
-               },
-               
-               _ajaxSetup: function() {
-                       return {
-                               data: {
-                                       className: 'wcf\\data\\user\\follow\\UserFollowAction'
-                               }
-                       };
-               }
-       });
-       
-       return UiUserProfileMenuItemFollow;
-});
-
-define('WoltLabSuite/Core/Ui/User/Profile/Menu/Item/Ignore',['Core', 'Language', 'Ui/Notification', './Abstract'], function(Core, Language, UiNotification, UiUserProfileMenuItemAbstract) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       _getLabel: function() {},
-                       _getAjaxActionName: function() {},
-                       _ajaxSuccess: function() {},
-                       _ajaxSetup: function() {},
-                       init: function() {},
-                       _initButton: function() {},
-                       _toggle: function() {},
-                       _updateButton: function() {}
-               };
-               return Fake;
-       }
-       
-       function UiUserProfileMenuItemIgnore(userId, isActive) { this.init(userId, isActive); }
-       Core.inherit(UiUserProfileMenuItemIgnore, UiUserProfileMenuItemAbstract, {
-               _getLabel: function() {
-                       return Language.get('wcf.user.button.' + (this._isActive ? 'un' : '') + 'ignore');
-               },
-               
-               _getAjaxActionName: function() {
-                       return this._isActive ? 'unignore' : 'ignore';
-               },
-               
-               _ajaxSuccess: function(data) {
-                       this._isActive = (data.returnValues.isIgnoredUser ? true : false);
-                       this._updateButton();
-                       
-                       UiNotification.show();
-               },
-               
-               _ajaxSetup: function() {
-                       return {
-                               data: {
-                                       className: 'wcf\\data\\user\\ignore\\UserIgnoreAction'
-                               }
-                       };
-               }
-       });
-       
-       return UiUserProfileMenuItemIgnore;
-});
-
-/*
- * Polyfill for `Element.prototype.matches()` and `Element.prototype.closest()`
- * Copyright (c) 2015 Jonathan Neal - https://github.com/jonathantneal/closest
- * License: CC0 1.0 Universal (https://creativecommons.org/publicdomain/zero/1.0/)
- */
-(function(ELEMENT) {
-       ELEMENT.matches = ELEMENT.matches || ELEMENT.mozMatchesSelector || ELEMENT.msMatchesSelector || ELEMENT.oMatchesSelector || ELEMENT.webkitMatchesSelector;
-       
-       ELEMENT.closest = ELEMENT.closest || function closest(selector) {
-                       var element = this;
-                       
-                       while (element) {
-                               if (element.matches(selector)) {
-                                       break;
-                               }
-                               
-                               element = element.parentElement;
-                       }
-                       
-                       return element;
-               };
-}(Element.prototype));
-
-define("closest", function(){});
-
-(function(window) {
-       var orgRequire = window.require;
-       var queue = [];
-       var counter = 0;
-
-       window.orgRequire = orgRequire
-       
-       window.require = function(dependencies, callback, errBack) {
-               if (!Array.isArray(dependencies)) {
-                       return orgRequire.apply(window, arguments);
-               }
-               
-               var promise = new Promise(function (resolve, reject) {
-                       var i = counter++;
-                       queue.push(i);
-                       
-                       orgRequire(dependencies, function () {
-                               var args = arguments;
-                               
-                               queue[queue.indexOf(i)] = function() { resolve(args); };
-                               
-                               executeCallbacks();
-                       }, function (err) {
-                               queue[queue.indexOf(i)] = function() { reject(err); };
-                               
-                               executeCallbacks();
-                       });
-               });
-               
-               if (callback) {
-                       promise = promise.then(function (objects) {
-                               return callback.apply(window, objects);
-                       });
-               }
-               if (errBack) {
-                       promise.catch(errBack);
-               }
-               
-               return promise;
-       };
-       window.require.config = orgRequire.config;
-       
-       function executeCallbacks() {
-               while (queue.length) {
-                       if (typeof queue[0] !== 'function') {
-                               break;
-                       }
-                       
-                       queue.shift()();
-               }
-       }
-})(window);
-
-define("require.linearExecution", function(){});
-
+var requirejs,require,define;!function(global,Promise,undef){function commentReplace(e,t){return t||""}function hasProp(e,t){return hasOwn.call(e,t)}function getOwn(e,t){return e&&hasProp(e,t)&&e[t]}function obj(){return Object.create(null)}function eachProp(e,t){var i;for(i in e)if(hasProp(e,i)&&t(e[i],i))break}function mixin(e,t,i,n){return t&&eachProp(t,function(t,a){!i&&hasProp(e,a)||(!n||"object"!=typeof t||!t||Array.isArray(t)||"function"==typeof t||t instanceof RegExp?e[a]=t:(e[a]||(e[a]={}),mixin(e[a],t,i,n)))}),e}function getGlobal(e){if(!e)return e;var t=global;return e.split(".").forEach(function(e){t=t[e]}),t}function newContext(e){function t(e){var t,i,n=e.length;for(t=0;t<n;t++)if("."===(i=e[t]))e.splice(t,1),t-=1;else if(".."===i){if(0===t||1===t&&".."===e[2]||".."===e[t-1])continue;t>0&&(e.splice(t-1,2),t-=2)}}function i(e,i,n){var a,r,o,s,l,c,d,u,h,p,f=i&&i.split("/"),m=f,g=B.map,v=g&&g["*"];if(e&&(e=e.split("/"),c=e.length-1,B.nodeIdCompat&&jsSuffixRegExp.test(e[c])&&(e[c]=e[c].replace(jsSuffixRegExp,"")),"."===e[0].charAt(0)&&f&&(m=f.slice(0,f.length-1),e=m.concat(e)),t(e),e=e.join("/")),n&&g&&(f||v)){r=e.split("/");e:for(o=r.length;o>0;o-=1){if(l=r.slice(0,o).join("/"),f)for(s=f.length;s>0;s-=1)if((a=getOwn(g,f.slice(0,s).join("/")))&&(a=getOwn(a,l))){d=a,u=o;break e}!h&&v&&getOwn(v,l)&&(h=getOwn(v,l),p=o)}!d&&h&&(d=h,u=p),d&&(r.splice(0,u,d),e=r.join("/"))}return getOwn(B.pkgs,e)||e}function n(e){function t(){var t;return e.init&&(t=e.init.apply(global,arguments)),t||e.exports&&getGlobal(e.exports)}return t}function a(e){var t,i,n,a;for(t=0;t<queue.length;t+=1){if("string"!=typeof queue[t][0]){if(!e)break;queue[t].unshift(e),e=undef}n=queue.shift(),i=n[0],t-=1,i in x||i in T||(i in M?C.apply(undef,n):T[i]=n)}e&&(a=getOwn(B.shim,e)||{},C(e,a.deps||[],a.exportsFn))}function r(e,t){var n=function(i,r,o,s){var l,c;if(t&&a(),"string"==typeof i){if(I[i])return I[i](e);if(!((l=E(i,e,!0).id)in x))throw new Error("Not loaded: "+l);return x[l]}return i&&!Array.isArray(i)&&(c=i,i=undef,Array.isArray(r)&&(i=r,r=o,o=s),t)?n.config(c)(i,r,o):(r=r||function(){return slice.call(arguments,0)},V.then(function(){return a(),C(undef,i||[],r,o,e)}))};return n.isBrowser="undefined"!=typeof document&&"undefined"!=typeof navigator,n.nameToUrl=function(e,t,i){var a,r,o,s,l,c,d,u=getOwn(B.pkgs,e);if(u&&(e=u),d=getOwn(H,e))return n.nameToUrl(d,t,i);if(urlRegExp.test(e))l=e+(t||"");else{for(a=B.paths,r=e.split("/"),o=r.length;o>0;o-=1)if(s=r.slice(0,o).join("/"),c=getOwn(a,s)){Array.isArray(c)&&(c=c[0]),r.splice(0,o,c);break}l=r.join("/"),l+=t||(/^data\:|^blob\:|\?/.test(l)||i?"":".js"),l=("/"===l.charAt(0)||l.match(/^[\w\+\.\-]+:/)?"":B.baseUrl)+l}return B.urlArgs&&!/^blob\:/.test(l)?l+B.urlArgs(e,l):l},n.toUrl=function(t){var a,r=t.lastIndexOf("."),o=t.split("/")[0],s="."===o||".."===o;return-1!==r&&(!s||r>1)&&(a=t.substring(r,t.length),t=t.substring(0,r)),n.nameToUrl(i(t,e),a,!0)},n.defined=function(t){return E(t,e,!0).id in x},n.specified=function(t){return(t=E(t,e,!0).id)in x||t in M},n}function o(e,t,i){e&&(x[e]=i,requirejs.onResourceLoad&&requirejs.onResourceLoad(D,t.map,t.deps)),t.finished=!0,t.resolve(i)}function s(e,t){e.finished=!0,e.rejected=!0,e.reject(t)}function l(e){return function(t){return i(t,e,!0)}}function c(e){e.factoryCalled=!0;var t,i=e.map.id;try{t=D.execCb(i,e.factory,e.values,x[i])}catch(t){return s(e,t)}i?t===undef&&(e.cjsModule?t=e.cjsModule.exports:e.usingExports&&(t=x[i])):N.splice(N.indexOf(e),1),o(i,e,t)}function d(e,t){this.rejected||this.depDefined[t]||(this.depDefined[t]=!0,this.depCount+=1,this.values[t]=e,this.depending||this.depCount!==this.depMax||c(this))}function u(e,t){var i={};return i.promise=new Promise(function(t,n){i.resolve=t,i.reject=function(t){e||N.splice(N.indexOf(i),1),n(t)}}),i.map=e?t||E(e):{},i.depCount=0,i.depMax=0,i.values=[],i.depDefined=[],i.depFinished=d,i.map.pr&&(i.deps=[E(i.map.pr)]),i}function h(e,t){var i;return e?(i=e in M&&M[e])||(i=M[e]=u(e,t)):(i=u(),N.push(i)),i}function p(e,t){return function(i){e.rejected||(i.dynaId||(i.dynaId="id"+(O+=1),i.requireModules=[t]),s(e,i))}}function f(e,t,i,n){i.depMax+=1,L(e,t).then(function(e){i.depFinished(e,n)},p(i,e.id)).catch(p(i,i.map.id))}function m(e){function t(t){i||o(e,h(e),t)}var i;return t.error=function(t){h(e).reject(t)},t.fromText=function(t,n){var r=h(e),o=E(E(e).n),l=o.id;i=!0,r.factory=function(e,t){return t},n&&(t=n),hasProp(B.config,e)&&(B.config[l]=B.config[e]);try{y.exec(t)}catch(e){s(r,new Error("fromText eval for "+l+" failed: "+e))}a(l),r.deps=[o],f(o,null,r,r.deps.length)},t}function g(e,t,i){e.load(t.n,r(i),m(t.id),B)}function v(e){var t,i=e?e.indexOf("!"):-1;return i>-1&&(t=e.substring(0,i),e=e.substring(i+1,e.length)),[t,e]}function _(e,t,i){var n=e.map.id;t[n]=!0,!e.finished&&e.deps&&e.deps.forEach(function(n){var a=n.id,r=!hasProp(I,a)&&h(a,n);!r||r.finished||i[a]||(hasProp(t,a)?e.deps.forEach(function(t,i){t.id===a&&e.depFinished(x[a],i)}):_(r,t,i))}),i[n]=!0}function b(e){var t,i,n,a=[],r=1e3*B.waitSeconds,o=r&&F+r<(new Date).getTime();if(0===U&&(e?e.finished||_(e,{},{}):N.length&&N.forEach(function(e){_(e,{},{})})),o){for(i in M)n=M[i],n.finished||a.push(n.map.id);t=new Error("Timeout for modules: "+a),t.requireModules=a,y.onError(t)}else(U||N.length)&&(S||(S=!0,setTimeout(function(){S=!1,b()},70)))}function w(e){return setTimeout(function(){e.dynaId&&W[e.dynaId]||(W[e.dynaId]=!0,y.onError(e))}),e}var y,C,E,L,I,S,A,D,x=obj(),T=obj(),B={waitSeconds:7,baseUrl:"./",paths:{},bundles:{},pkgs:{},shim:{},config:{}},k=obj(),N=[],M=obj(),j=obj(),P=obj(),U=0,F=(new Date).getTime(),O=0,W=obj(),R=obj(),H=obj(),V=Promise.resolve();return A="function"==typeof importScripts?function(e){var t=e.url;R[t]||(R[t]=!0,h(e.id),importScripts(t),a(e.id))}:function(e){var t,i=e.id,n=e.url;R[n]||(R[n]=!0,t=document.createElement("script"),t.setAttribute("data-requiremodule",i),t.type=B.scriptType||"text/javascript",t.charset="utf-8",t.async=!0,U+=1,t.addEventListener("load",function(){U-=1,a(i)},!1),t.addEventListener("error",function(){U-=1;var e,n=getOwn(B.paths,i);if(n&&Array.isArray(n)&&n.length>1){t.parentNode.removeChild(t),n.shift();var a=h(i);a.map=E(i),a.map.url=y.nameToUrl(i),A(a.map)}else e=new Error("Load failed: "+i+": "+t.src),e.requireModules=[i],h(i).reject(e)},!1),t.src=n,10===document.documentMode?asap.then(function(){document.head.appendChild(t)}):document.head.appendChild(t))},L=function(e,t){var i,n,a=e.id,r=B.shim[a];if(a in T)i=T[a],delete T[a],C.apply(undef,i);else if(!(a in M))if(e.pr){if(!(n=getOwn(H,a)))return L(E(e.pr)).then(function(i){var n=e.prn?e:E(a,t,!0),r=n.id,o=getOwn(B.shim,r);return r in P||(P[r]=!0,o&&o.deps?y(o.deps,function(){g(i,n,t)}):g(i,n,t)),h(r).promise});e.url=y.nameToUrl(n),A(e)}else r&&r.deps?y(r.deps,function(){A(e)}):A(e);return h(a).promise},E=function(e,t,n){if("string"!=typeof e)return e;var a,r,o,s,c,d,u=e+" & "+(t||"")+" & "+!!n;return o=v(e),s=o[0],e=o[1],!s&&u in k?k[u]:(s&&(s=i(s,t,n),a=s in x&&x[s]),s?a&&a.normalize?(e=a.normalize(e,l(t)),d=!0):e=-1===e.indexOf("!")?i(e,t,n):e:(e=i(e,t,n),o=v(e),s=o[0],e=o[1],r=y.nameToUrl(e)),c={id:s?s+"!"+e:e,n:e,pr:s,url:r,prn:s&&d},s||(k[u]=c),c)},I={require:function(e){return r(e)},exports:function(e){var t=x[e];return void 0!==t?t:x[e]={}},module:function(e){return{id:e,uri:"",exports:I.exports(e),config:function(){return getOwn(B.config,e)||{}}}}},C=function(e,t,i,n,a){if(e){if(e in j)return;j[e]=!0}var r=h(e);return t&&!Array.isArray(t)&&(i=t,t=[]),t=t?slice.call(t,0):null,n||(hasProp(B,"defaultErrback")?B.defaultErrback&&(n=B.defaultErrback):n=w),n&&r.promise.catch(n),a=a||e,"function"==typeof i?(!t.length&&i.length&&(i.toString().replace(commentRegExp,commentReplace).replace(cjsRequireRegExp,function(e,i){t.push(i)}),t=(1===i.length?["require"]:["require","exports","module"]).concat(t)),r.factory=i,r.deps=t,r.depending=!0,t.forEach(function(i,n){var o;t[n]=o=E(i,a,!0),i=o.id,"require"===i?r.values[n]=I.require(e):"exports"===i?(r.values[n]=I.exports(e),r.usingExports=!0):"module"===i?r.values[n]=r.cjsModule=I.module(e):void 0===i?r.values[n]=void 0:f(o,a,r,n)}),r.depending=!1,r.depCount===r.depMax&&c(r)):e&&o(e,r,i),F=(new Date).getTime(),e||b(r),r.promise},y=r(null,!0),y.config=function(t){if(t.context&&t.context!==e){var i=getOwn(contexts,t.context);return i?i.req.config(t):newContext(t.context).config(t)}if(k=obj(),t.baseUrl&&"/"!==t.baseUrl.charAt(t.baseUrl.length-1)&&(t.baseUrl+="/"),"string"==typeof t.urlArgs){var a=t.urlArgs;t.urlArgs=function(e,t){return(-1===t.indexOf("?")?"?":"&")+a}}var r=B.shim,o={paths:!0,bundles:!0,config:!0,map:!0};return eachProp(t,function(e,t){o[t]?(B[t]||(B[t]={}),mixin(B[t],e,!0,!0)):B[t]=e}),t.bundles&&eachProp(t.bundles,function(e,t){e.forEach(function(e){e!==t&&(H[e]=t)})}),t.shim&&(eachProp(t.shim,function(e,t){Array.isArray(e)&&(e={deps:e}),!e.exports&&!e.init||e.exportsFn||(e.exportsFn=n(e)),r[t]=e}),B.shim=r),t.packages&&t.packages.forEach(function(e){var t,i;e="string"==typeof e?{name:e}:e,i=e.name,t=e.location,t&&(B.paths[i]=e.location),B.pkgs[i]=e.name+"/"+(e.main||"main").replace(currDirRegExp,"").replace(jsSuffixRegExp,"")}),(t.deps||t.callback)&&y(t.deps,t.callback),y},y.onError=function(e){throw e},D={id:e,defined:x,waiting:T,config:B,deferreds:M,req:y,execCb:function(e,t,i,n){return t.apply(n,i)}},contexts[e]=D,y}if(!Promise)throw new Error("No Promise implementation available");var topReq,dataMain,src,subPath,bootstrapConfig=requirejs||require,hasOwn=Object.prototype.hasOwnProperty,contexts={},queue=[],currDirRegExp=/^\.\//,urlRegExp=/^\/|\:|\?|\.js$/,commentRegExp=/\/\*[\s\S]*?\*\/|([^:"'=]|^)\/\/.*$/gm,cjsRequireRegExp=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,jsSuffixRegExp=/\.js$/,slice=Array.prototype.slice;if("function"!=typeof requirejs){var asap=Promise.resolve(void 0);requirejs=topReq=newContext("_"),"function"!=typeof require&&(require=topReq),topReq.exec=function(text){return eval(text)},topReq.contexts=contexts,define=function(){queue.push(slice.call(arguments,0))},define.amd={jQuery:!0},bootstrapConfig&&topReq.config(bootstrapConfig),topReq.isBrowser&&!contexts._.config.skipDataMain&&(dataMain=document.querySelectorAll("script[data-main]")[0],(dataMain=dataMain&&dataMain.getAttribute("data-main"))&&(dataMain=dataMain.replace(jsSuffixRegExp,""),bootstrapConfig&&bootstrapConfig.baseUrl||-1!==dataMain.indexOf("!")||(src=dataMain.split("/"),dataMain=src.pop(),subPath=src.length?src.join("/")+"/":"./",topReq.config({baseUrl:subPath})),topReq([dataMain])))}}(this,"undefined"!=typeof Promise?Promise:void 0),define("requireLib",function(){}),requirejs.config({paths:{enquire:"3rdParty/enquire",favico:"3rdParty/favico","perfect-scrollbar":"3rdParty/perfect-scrollbar",Pica:"3rdParty/pica",prism:"3rdParty/prism"},shim:{enquire:{exports:"enquire"},favico:{exports:"Favico"},"perfect-scrollbar":{exports:"PerfectScrollbar"}},map:{"*":{Ajax:"WoltLabSuite/Core/Ajax",AjaxJsonp:"WoltLabSuite/Core/Ajax/Jsonp",AjaxRequest:"WoltLabSuite/Core/Ajax/Request",CallbackList:"WoltLabSuite/Core/CallbackList",ColorUtil:"WoltLabSuite/Core/ColorUtil",Core:"WoltLabSuite/Core/Core",DateUtil:"WoltLabSuite/Core/Date/Util",Devtools:"WoltLabSuite/Core/Devtools",Dictionary:"WoltLabSuite/Core/Dictionary","Dom/ChangeListener":"WoltLabSuite/Core/Dom/Change/Listener","Dom/Traverse":"WoltLabSuite/Core/Dom/Traverse","Dom/Util":"WoltLabSuite/Core/Dom/Util",Environment:"WoltLabSuite/Core/Environment",EventHandler:"WoltLabSuite/Core/Event/Handler",EventKey:"WoltLabSuite/Core/Event/Key",Language:"WoltLabSuite/Core/Language",List:"WoltLabSuite/Core/List",ObjectMap:"WoltLabSuite/Core/ObjectMap",Permission:"WoltLabSuite/Core/Permission",StringUtil:"WoltLabSuite/Core/StringUtil","Ui/Alignment":"WoltLabSuite/Core/Ui/Alignment","Ui/CloseOverlay":"WoltLabSuite/Core/Ui/CloseOverlay","Ui/Confirmation":"WoltLabSuite/Core/Ui/Confirmation","Ui/Dialog":"WoltLabSuite/Core/Ui/Dialog","Ui/Notification":"WoltLabSuite/Core/Ui/Notification","Ui/ReusableDropdown":"WoltLabSuite/Core/Ui/Dropdown/Reusable","Ui/Screen":"WoltLabSuite/Core/Ui/Screen","Ui/Scroll":"WoltLabSuite/Core/Ui/Scroll","Ui/SimpleDropdown":"WoltLabSuite/Core/Ui/Dropdown/Simple","Ui/TabMenu":"WoltLabSuite/Core/Ui/TabMenu",Upload:"WoltLabSuite/Core/Upload",User:"WoltLabSuite/Core/User"}},waitSeconds:0}),define("jquery",[],function(){return window.jQuery}),define("require.config",function(){}),function(e,t){e.elAttr=function(e,t,i){if(void 0===i)return e.getAttribute(t)||"";e.setAttribute(t,i)},e.elAttrBool=function(e,t){var i=elAttr(e,t);return"1"===i||"true"===i},e.elByClass=function(e,i){return(i||t).getElementsByClassName(e)},e.elById=function(e){return t.getElementById(e)},e.elBySel=function(e,i){return(i||t).querySelector(e)},e.elBySelAll=function(e,i,n){var a=(i||t).querySelectorAll(e);return"function"==typeof n&&Array.prototype.forEach.call(a,n),a},e.elByTag=function(e,i){return(i||t).getElementsByTagName(e)},e.elCreate=function(e){return t.createElement(e)},e.elClosest=function(e,t){if(!(e instanceof Node))throw new TypeError("Provided element is not a Node.");return e.nodeType===Node.TEXT_NODE&&null===(e=e.parentNode)?null:("string"!=typeof t&&(t=""),0===t.length?e:e.closest(t))},e.elData=function(e,t,i){if(t="data-"+t,void 0===i)return e.getAttribute(t)||"";e.setAttribute(t,i)},e.elDataBool=function(e,t){var i=elData(e,t);return"1"===i||"true"===i},e.elHide=function(e){e.style.setProperty("display","none","")},e.elIsHidden=function(e){return"none"===e.style.getPropertyValue("display")},e.elInnerError=function(e,t,i){var n=e.parentNode;if(null===n)throw new Error("Only elements that have a parent element or document are valid.");if("string"!=typeof t){if(void 0!==t&&null!==t&&!1!==t)throw new TypeError("The error message must be a string; `false`, `null` or `undefined` can be used as a substitute for an empty string.");t=""}var a=e.nextElementSibling;return null!==a&&"SMALL"===a.nodeName&&a.classList.contains("innerError")||(""===t?a=null:(a=elCreate("small"),a.className="innerError",n.insertBefore(a,e.nextSibling))),""===t?null!==a&&(n.removeChild(a),a=null):a[i?"innerHTML":"textContent"]=t,a},e.elRemove=function(e){e.parentNode.removeChild(e)},e.elShow=function(e){e.style.removeProperty("display")},e.elToggle=function(e){"none"===e.style.getPropertyValue("display")?elShow(e):elHide(e)},e.forEach=function(e,t){for(var i=0,n=e.length;i<n;i++)t(e[i],i)},e.objOwns=function(e,t){return e.hasOwnProperty(t)};"touchstart"in t.documentElement||"ontouchstart"in e||navigator.MaxTouchPoints>0||navigator.msMaxTouchPoints;Object.defineProperty(e,"WCF_CLICK_EVENT",{value:"click"}),function(){function t(){e.history.state&&e.history.state.name&&"initial"!==e.history.state.name?(e.history.replaceState({name:"skip",depth:++i},""),e.history.back(),setTimeout(t,1)):e.history.replaceState({name:"initial"},"")}var i=0;t(),e.addEventListener("popstate",function(t){t.state&&t.state.name&&"skip"===t.state.name&&e.history.go(t.state.depth)})}(),e.String.prototype.hashCode=function(){var e,t=0;if(this.length)for(var i=0,n=this.length;i<n;i++)e=this.charCodeAt(i),t=(t<<5)-t+e,t&=t;return t}}(window,document),define("wcf.globalHelper",function(){}),define("WoltLabSuite/Core/Core",[],function(){"use strict";var e=function(e){return"object"==typeof e&&(Array.isArray(e)||n.isPlainObject(e))?t(e):e},t=function(t){if(!t)return null;if(Array.isArray(t))return t.slice();var i={};for(var n in t)t.hasOwnProperty(n)&&void 0!==t[n]&&(i[n]=e(t[n]));return i},i="wsc"+window.WCF_PATH.hashCode()+"-",n={clone:function(t){return e(t)},convertLegacyUrl:function(e){return e.replace(/^index\.php\/(.*?)\/\?/,function(e,t){var i=t.split(/([A-Z][a-z0-9]+)/);t="";for(var n=0,a=i.length;n<a;n++){var r=i[n].trim();r.length&&(t.length&&(t+="-"),t+=r.toLowerCase())}return"index.php?"+t+"/&"})},extend:function(e){e=e||{};for(var t=this.clone(e),i=1,n=arguments.length;i<n;i++){var a=arguments[i];if(a)for(var r in a)objOwns(a,r)&&(Array.isArray(a[r])||"object"!=typeof a[r]?t[r]=a[r]:this.isPlainObject(a[r])?t[r]=this.extend(e[r],a[r]):t[r]=a[r])}return t},inherit:function(e,t,i){if(void 0===e||null===e)throw new TypeError("The constructor must not be undefined or null.");if(void 0===t||null===t)throw new TypeError("The super constructor must not be undefined or null.");if(void 0===t.prototype)throw new TypeError("The super constructor must have a prototype.");e._super=t,e.prototype=n.extend(Object.create(t.prototype,{constructor:{configurable:!0,enumerable:!1,value:e,writable:!0}}),i||{})},isPlainObject:function(e){return"object"==typeof e&&null!==e&&!e.nodeType&&Object.getPrototypeOf(e)===Object.prototype},getType:function(e){return Object.prototype.toString.call(e).replace(/^\[object (.+)\]$/,"$1")},getUuid:function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){var t=16*Math.random()|0;return("x"==e?t:3&t|8).toString(16)})},serialize:function(e,t){var i=[];for(var n in e)if(objOwns(e,n)){var a=t?t+"["+n+"]":n,r=e[n];"object"==typeof r?i.push(this.serialize(r,a)):i.push(encodeURIComponent(a)+"="+encodeURIComponent(r))}return i.join("&")},triggerEvent:function(e,t){var i;try{i=new Event(t,{bubbles:!0,cancelable:!0})}catch(e){i=document.createEvent("Event"),i.initEvent(t,!0,!0)}e.dispatchEvent(i)},getStoragePrefix:function(){return i}};return n}),define("WoltLabSuite/Core/Dictionary",["Core"],function(e){"use strict";function t(){this._dictionary=i?new Map:{}}var i=objOwns(window,"Map")&&"function"==typeof window.Map;return t.prototype={set:function(e,t){if("number"==typeof e&&(e=e.toString()),"string"!=typeof e)throw new TypeError("Only strings can be used as keys, rejected '"+e+"' ("+typeof e+").");i?this._dictionary.set(e,t):this._dictionary[e]=t},delete:function(e){"number"==typeof e&&(e=e.toString()),i?this._dictionary.delete(e):this._dictionary[e]=void 0},has:function(e){return"number"==typeof e&&(e=e.toString()),i?this._dictionary.has(e):objOwns(this._dictionary,e)&&void 0!==this._dictionary[e]},get:function(e){if("number"==typeof e&&(e=e.toString()),this.has(e))return i?this._dictionary.get(e):this._dictionary[e]},forEach:function(e){if("function"!=typeof e)throw new TypeError("forEach() expects a callback as first parameter.");if(i)this._dictionary.forEach(e);else for(var t=Object.keys(this._dictionary),n=0,a=t.length;n<a;n++)e(this._dictionary[t[n]],t[n])},merge:function(){for(var e=0,i=arguments.length;e<i;e++){var n=arguments[e];if(!(n instanceof t))throw new TypeError("Expected an object of type Dictionary, but argument "+e+" is not.");n.forEach(function(e,t){this.set(t,e)}.bind(this))}},toObject:function(){if(!i)return e.clone(this._dictionary);var t={};return this._dictionary.forEach(function(e,i){t[i]=e}),t}},t.fromObject=function(e){var i=new t;for(var n in e)objOwns(e,n)&&i.set(n,e[n]);return i},Object.defineProperty(t.prototype,"size",{enumerable:!1,configurable:!0,get:function(){return i?this._dictionary.size:Object.keys(this._dictionary).length}}),t}),define("WoltLabSuite/Core/Template.grammar",["require"],function(e){var t=function(e,t,i,n){for(i=i||{},n=e.length;n--;i[e[n]]=t);return i},i=[2,37],n=[5,9,11,12,13,18,19,21,22,23,25,26,27,28,30,31,32,33,35,37,39],a=[1,24],r=[1,25],o=[1,31],s=[1,29],l=[1,30],c=[1,26],d=[1,27],u=[1,33],h=[11,12,15,40,41,45,47,49,50,52],p=[9,11,12,13,18,19,21,23,26,28,30,31,32,33,35,37],f=[11,12,15,40,41,44,45,46,47,49,50,52],m=[18,35,37],g=[12,15],v={trace:function(){},yy:{},symbols_:{error:2,TEMPLATE:3,CHUNK_STAR:4,EOF:5,CHUNK_STAR_repetition0:6,CHUNK:7,PLAIN_ANY:8,T_LITERAL:9,COMMAND:10,T_ANY:11,T_WS:12,"{if":13,COMMAND_PARAMETERS:14,"}":15,COMMAND_repetition0:16,COMMAND_option0:17,"{/if}":18,"{include":19,COMMAND_PARAMETER_LIST:20,"{implode":21,"{/implode}":22,"{foreach":23,COMMAND_option1:24,"{/foreach}":25,"{lang}":26,"{/lang}":27,"{":28,VARIABLE:29,"{#":30,"{@":31,"{ldelim}":32,"{rdelim}":33,ELSE:34,"{else}":35,ELSE_IF:36,"{elseif":37,FOREACH_ELSE:38,"{foreachelse}":39,T_VARIABLE:40,T_VARIABLE_NAME:41,VARIABLE_repetition0:42,VARIABLE_SUFFIX:43,"[":44,"]":45,".":46,"(":47,VARIABLE_SUFFIX_option0:48,")":49,"=":50,COMMAND_PARAMETER_VALUE:51,T_QUOTED_STRING:52,COMMAND_PARAMETERS_repetition_plus0:53,COMMAND_PARAMETER:54,$accept:0,$end:1},terminals_:{2:"error",5:"EOF",9:"T_LITERAL",11:"T_ANY",12:"T_WS",13:"{if",15:"}",18:"{/if}",19:"{include",21:"{implode",22:"{/implode}",23:"{foreach",25:"{/foreach}",26:"{lang}",27:"{/lang}",28:"{",30:"{#",31:"{@",32:"{ldelim}",33:"{rdelim}",35:"{else}",37:"{elseif",39:"{foreachelse}",40:"T_VARIABLE",41:"T_VARIABLE_NAME",44:"[",45:"]",46:".",47:"(",49:")",50:"=",52:"T_QUOTED_STRING"},productions_:[0,[3,2],[4,1],[7,1],[7,1],[7,1],[8,1],[8,1],[10,7],[10,3],[10,5],[10,6],[10,3],[10,3],[10,3],[10,3],[10,1],[10,1],[34,2],[36,4],[38,2],[29,3],[43,3],[43,2],[43,3],[20,5],[20,3],[51,1],[51,1],[14,1],[54,1],[54,1],[54,1],[54,1],[54,1],[54,1],[54,3],[6,0],[6,2],[16,0],[16,2],[17,0],[17,1],[24,0],[24,1],[42,0],[42,2],[48,0],[48,1],[53,1],[53,2]],performAction:function(e,t,i,n,a,r,o){var s=r.length-1;switch(a){case 1:return r[s-1]+";";case 2:var l=r[s].reduce(function(e,t){return t.encode&&!e[1]?e[0]+=" + '"+t.value:t.encode&&e[1]?e[0]+=t.value:!t.encode&&e[1]?e[0]+="' + "+t.value:t.encode||e[1]||(e[0]+=" + "+t.value),e[1]=t.encode,e},["''",!1]);l[1]&&(l[0]+="'"),this.$=l[0];break;case 3:case 4:this.$={encode:!0,value:r[s].replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/(\r\n|\n|\r)/g,"\\n")};break;case 5:this.$={encode:!1,value:r[s]};break;case 8:this.$="(function() { if ("+r[s-5]+") { return "+r[s-3]+"; } "+r[s-2].join(" ")+" "+(r[s-1]||"")+" return ''; })()";break;case 9:if(!r[s-1].file)throw new Error("Missing parameter file");this.$=r[s-1].file+".fetch(v)";break;case 10:if(!r[s-3].from)throw new Error("Missing parameter from");if(!r[s-3].item)throw new Error("Missing parameter item");r[s-3].glue||(r[s-3].glue="', '"),this.$="(function() { return "+r[s-3].from+".map(function(item) { v["+r[s-3].item+"] = item; return "+r[s-1]+"; }).join("+r[s-3].glue+"); })()";break;case 11:if(!r[s-4].from)throw new Error("Missing parameter from");if(!r[s-4].item)throw new Error("Missing parameter item");this.$="(function() {var looped = false, result = '';if ("+r[s-4].from+" instanceof Array) {for (var i = 0; i < "+r[s-4].from+".length; i++) { looped = true;v["+r[s-4].key+"] = i;v["+r[s-4].item+"] = "+r[s-4].from+"[i];result += "+r[s-2]+";}} else {for (var key in "+r[s-4].from+") {if (!"+r[s-4].from+".hasOwnProperty(key)) continue;looped = true;v["+r[s-4].key+"] = key;v["+r[s-4].item+"] = "+r[s-4].from+"[key];result += "+r[s-2]+";}}return (looped ? result : "+(r[s-1]||"''")+"); })()";break;case 12:this.$="Language.get("+r[s-1]+", v)";break;case 13:this.$="StringUtil.escapeHTML("+r[s-1]+")";break;case 14:this.$="StringUtil.formatNumeric("+r[s-1]+")";break;case 15:this.$=r[s-1];break;case 16:this.$="'{'";break;case 17:this.$="'}'";break;case 18:this.$="else { return "+r[s]+"; }";break;case 19:this.$="else if ("+r[s-2]+") { return "+r[s]+"; }";break;case 20:this.$=r[s];break;case 21:this.$="v['"+r[s-1]+"']"+r[s].join("");break;case 22:this.$=r[s-2]+r[s-1]+r[s];break;case 23:this.$="['"+r[s]+"']";break;case 24:case 36:this.$=r[s-2]+(r[s-1]||"")+r[s];break;case 25:this.$=r[s],this.$[r[s-4]]=r[s-2];break;case 26:this.$={},this.$[r[s-2]]=r[s];break;case 29:this.$=r[s].join("");break;case 37:case 39:case 45:this.$=[];break;case 38:case 40:case 46:case 50:r[s-1].push(r[s]);break;case 49:this.$=[r[s]]}},table:[t([5,9,11,12,13,19,21,23,26,28,30,31,32,33],i,{3:1,4:2,6:3}),{1:[3]},{5:[1,4]},t([5,18,22,25,27,35,37,39],[2,2],{7:5,8:6,10:8,9:[1,7],11:[1,9],12:[1,10],13:[1,11],19:[1,12],21:[1,13],23:[1,14],26:[1,15],28:[1,16],30:[1,17],31:[1,18],32:[1,19],33:[1,20]}),{1:[2,1]},t(n,[2,38]),t(n,[2,3]),t(n,[2,4]),t(n,[2,5]),t(n,[2,6]),t(n,[2,7]),{11:a,12:r,14:21,29:28,40:o,41:s,47:l,50:c,52:d,53:22,54:23},{20:32,41:u},{20:34,41:u},{20:35,41:u},t([9,11,12,13,19,21,23,26,27,28,30,31,32,33],i,{6:3,4:36}),{29:37,40:o},{29:38,40:o},{29:39,40:o},t(n,[2,16]),t(n,[2,17]),{15:[1,40]},t([15,45,49],[2,29],{29:28,54:41,11:a,12:r,40:o,41:s,47:l,50:c,52:d}),t(h,[2,49]),t(h,[2,30]),t(h,[2,31]),t(h,[2,32]),t(h,[2,33]),t(h,[2,34]),t(h,[2,35]),{11:a,12:r,14:42,29:28,40:o,41:s,47:l,50:c,52:d,53:22,54:23},{41:[1,43]},{15:[1,44]},{50:[1,45]},{15:[1,46]},{15:[1,47]},{27:[1,48]},{15:[1,49]},{15:[1,50]},{15:[1,51]},t(p,i,{6:3,4:52}),t(h,[2,50]),{49:[1,53]},t(f,[2,45],{42:54}),t(n,[2,9]),{29:57,40:o,51:55,52:[1,56]},t([9,11,12,13,19,21,22,23,26,28,30,31,32,33],i,{6:3,4:58}),t([9,11,12,13,19,21,23,25,26,28,30,31,32,33,39],i,{6:3,4:59}),t(n,[2,12]),t(n,[2,13]),t(n,[2,14]),t(n,[2,15]),t(m,[2,39],{16:60}),t(h,[2,36]),t([11,12,15,40,41,45,49,50,52],[2,21],{43:61,44:[1,62],46:[1,63],47:[1,64]}),{12:[1,65],15:[2,26]},t(g,[2,27]),t(g,[2,28]),{22:[1,66]},{24:67,25:[2,43],38:68,39:[1,69]},{17:70,18:[2,41],34:72,35:[1,74],36:71,37:[1,73]},t(f,[2,46]),{11:a,12:r,14:75,29:28,40:o,41:s,47:l,50:c,52:d,53:22,54:23},{41:[1,76]},{11:a,12:r,14:78,29:28,40:o,41:s,47:l,48:77,49:[2,47],50:c,52:d,53:22,54:23},{20:79,41:u},t(n,[2,10]),{25:[1,80]},{25:[2,44]},t([9,11,12,13,19,21,23,25,26,28,30,31,32,33],i,{6:3,4:81}),{18:[1,82]},t(m,[2,40]),{18:[2,42]},{11:a,12:r,14:83,29:28,40:o,41:s,47:l,50:c,52:d,53:22,54:23},t([9,11,12,13,18,19,21,23,26,28,30,31,32,33],i,{6:3,4:84}),{45:[1,85]},t(f,[2,23]),{49:[1,86]},{49:[2,48]},{15:[2,25]},t(n,[2,11]),{25:[2,20]},t(n,[2,8]),{15:[1,87]},{18:[2,18]},t(f,[2,22]),t(f,[2,24]),t(p,i,{6:3,4:88}),t(m,[2,19])],defaultActions:{4:[2,1],68:[2,44],72:[2,42],78:[2,48],79:[2,25],81:[2,20],84:[2,18]},parseError:function(e,t){function i(e,t){this.message=e,this.hash=t}if(!t.recoverable)throw i.prototype=Error,new i(e,t);this.trace(e)},parse:function(e){var t=this,i=[0],n=[null],a=[],r=this.table,o="",s=0,l=0,c=0,d=a.slice.call(arguments,1),u=Object.create(this.lexer),h={yy:{}};for(var p in this.yy)Object.prototype.hasOwnProperty.call(this.yy,p)&&(h.yy[p]=this.yy[p]);u.setInput(e,h.yy),h.yy.lexer=u,h.yy.parser=this,void 0===u.yylloc&&(u.yylloc={});var f=u.yylloc;a.push(f);var m=u.options&&u.options.ranges;"function"==typeof h.yy.parseError?this.parseError=h.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var g,v,_,b,w,y,C,E,L,I=function(){var e;return e=u.lex()||1,"number"!=typeof e&&(e=t.symbols_[e]||e),e},S={};;){if(_=i[i.length-1],this.defaultActions[_]?b=this.defaultActions[_]:(null!==g&&void 0!==g||(g=I()),b=r[_]&&r[_][g]),void 0===b||!b.length||!b[0]){var A="";L=[];for(y in r[_])this.terminals_[y]&&y>2&&L.push("'"+this.terminals_[y]+"'");A=u.showPosition?"Parse error on line "+(s+1)+":\n"+u.showPosition()+"\nExpecting "+L.join(", ")+", got '"+(this.terminals_[g]||g)+"'":"Parse error on line "+(s+1)+": Unexpected "+(1==g?"end of input":"'"+(this.terminals_[g]||g)+"'"),this.parseError(A,{text:u.match,token:this.terminals_[g]||g,line:u.yylineno,loc:f,expected:L})}if(b[0]instanceof Array&&b.length>1)throw new Error("Parse Error: multiple actions possible at state: "+_+", token: "+g);switch(b[0]){case 1:i.push(g),n.push(u.yytext),a.push(u.yylloc),i.push(b[1]),g=null,v?(g=v,v=null):(l=u.yyleng,o=u.yytext,s=u.yylineno,f=u.yylloc,c>0&&c--);break;case 2:if(C=this.productions_[b[1]][1],S.$=n[n.length-C],S._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(S._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(w=this.performAction.apply(S,[o,l,s,h.yy,b[1],n,a].concat(d))))return w;C&&(i=i.slice(0,-1*C*2),n=n.slice(0,-1*C),a=a.slice(0,-1*C)),i.push(this.productions_[b[1]][0]),n.push(S.$),a.push(S._$),E=r[i[i.length-2]][i[i.length-1]],i.push(E);break;case 3:return!0}}return!0}},_=function(){return{EOF:1,parseError:function(e,t){if(!this.yy.parser)throw new Error(e);this.yy.parser.parseError(e,t)},setInput:function(e,t){return this.yy=t||this.yy||{},this._input=e,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var e=this._input[0];return this.yytext+=e,this.yyleng++,this.offset++,this.match+=e,this.matched+=e,e.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),e},unput:function(e){var t=e.length,i=e.split(/(?:\r\n?|\n)/g);this._input=e+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-t),this.offset-=t;var n=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),i.length-1&&(this.yylineno-=i.length-1);var a=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:i?(i.length===n.length?this.yylloc.first_column:0)+n[n.length-i.length].length-i[0].length:this.yylloc.first_column-t},this.options.ranges&&(this.yylloc.range=[a[0],a[0]+this.yyleng-t]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(e){this.unput(this.match.slice(e))},pastInput:function(){var e=this.matched.substr(0,this.matched.length-this.match.length);return(e.length>20?"...":"")+e.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var e=this.match;return e.length<20&&(e+=this._input.substr(0,20-e.length)),(e.substr(0,20)+(e.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var e=this.pastInput(),t=new Array(e.length+1).join("-");return e+this.upcomingInput()+"\n"+t+"^"},test_match:function(e,t){var i,n,a;if(this.options.backtrack_lexer&&(a={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(a.yylloc.range=this.yylloc.range.slice(0))),n=e[0].match(/(?:\r\n?|\n).*/g),n&&(this.yylineno+=n.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:n?n[n.length-1].length-n[n.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+e[0].length},this.yytext+=e[0],this.match+=e[0],this.matches=e,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(e[0].length),this.matched+=e[0],i=this.performAction.call(this,this.yy,this,t,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),i)return i;if(this._backtrack){for(var r in a)this[r]=a[r];return!1}return!1},next:function(){if(this.done)return this.EOF;this._input||(this.done=!0);var e,t,i,n;this._more||(this.yytext="",this.match="");for(var a=this._currentRules(),r=0;r<a.length;r++)if((i=this._input.match(this.rules[a[r]]))&&(!t||i[0].length>t[0].length)){if(t=i,n=r,this.options.backtrack_lexer){if(!1!==(e=this.test_match(i,a[r])))return e;if(this._backtrack){t=!1;continue}return!1}if(!this.options.flex)break}return t?!1!==(e=this.test_match(t,a[n]))&&e:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){
+var e=this.next();return e||this.lex()},begin:function(e){this.conditionStack.push(e)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(e){return e=this.conditionStack.length-1-Math.abs(e||0),e>=0?this.conditionStack[e]:"INITIAL"},pushState:function(e){this.begin(e)},stateStackSize:function(){return this.conditionStack.length},options:{},performAction:function(e,t,i,n){switch(i){case 0:break;case 1:return t.yytext=t.yytext.substring(9,t.yytext.length-10),9;case 2:case 3:return 52;case 4:return 40;case 5:return 41;case 6:return 46;case 7:return 44;case 8:return 45;case 9:return 47;case 10:return 49;case 11:return 50;case 12:return 32;case 13:return 33;case 14:return this.begin("command"),30;case 15:return this.begin("command"),31;case 16:return this.begin("command"),13;case 17:case 18:return this.begin("command"),37;case 19:return 35;case 20:return 18;case 21:return 26;case 22:return 27;case 23:return this.begin("command"),19;case 24:return this.begin("command"),21;case 25:return 22;case 26:return this.begin("command"),23;case 27:return 39;case 28:return 25;case 29:return this.begin("command"),28;case 30:return this.popState(),15;case 31:return 12;case 32:return 5;case 33:return 11}},rules:[/^(?:\{\*[\s\S]*?\*\})/,/^(?:\{literal\}[\s\S]*?\{\/literal\})/,/^(?:"([^"]|\\\.)*")/,/^(?:'([^']|\\\.)*')/,/^(?:\$)/,/^(?:[_a-zA-Z][_a-zA-Z0-9]*)/,/^(?:\.)/,/^(?:\[)/,/^(?:\])/,/^(?:\()/,/^(?:\))/,/^(?:=)/,/^(?:\{ldelim\})/,/^(?:\{rdelim\})/,/^(?:\{#)/,/^(?:\{@)/,/^(?:\{if )/,/^(?:\{else if )/,/^(?:\{elseif )/,/^(?:\{else\})/,/^(?:\{\/if\})/,/^(?:\{lang\})/,/^(?:\{\/lang\})/,/^(?:\{include )/,/^(?:\{implode )/,/^(?:\{\/implode\})/,/^(?:\{foreach )/,/^(?:\{foreachelse\})/,/^(?:\{\/foreach\})/,/^(?:\{(?!\s))/,/^(?:\})/,/^(?:\s+)/,/^(?:$)/,/^(?:[^{])/],conditions:{command:{rules:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33],inclusive:!0},INITIAL:{rules:[0,1,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,31,32,33],inclusive:!0}}}}();return v.lexer=_,v}),define("WoltLabSuite/Core/NumberUtil",[],function(){"use strict";return{round:function(e,t){return void 0===t||0==+t?Math.round(e):(e=+e,t=+t,isNaN(e)||"number"!=typeof t||t%1!=0?NaN:(e=e.toString().split("e"),e=Math.round(+(e[0]+"e"+(e[1]?+e[1]-t:-t))),e=e.toString().split("e"),+(e[0]+"e"+(e[1]?+e[1]+t:t))))}}}),define("WoltLabSuite/Core/StringUtil",["Language","./NumberUtil"],function(e,t){"use strict";return{addThousandsSeparator:function(t){return void 0===e&&(e=require("Language")),String(t).replace(/(^-?\d{1,3}|\d{3})(?=(?:\d{3})+(?:$|\.))/g,"$1"+e.get("wcf.global.thousandsSeparator"))},escapeHTML:function(e){return String(e).replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")},escapeRegExp:function(e){return String(e).replace(/([.*+?^=!:${}()|[\]\/\\])/g,"\\$1")},formatNumeric:function(i,n){void 0===e&&(e=require("Language")),i=String(t.round(i,n||-2));var a=i.split(".");return i=this.addThousandsSeparator(a[0]),a.length>1&&(i+=e.get("wcf.global.decimalPoint")+a[1]),i=i.replace("-","−")},lcfirst:function(e){return String(e).substring(0,1).toLowerCase()+e.substring(1)},ucfirst:function(e){return String(e).substring(0,1).toUpperCase()+e.substring(1)},unescapeHTML:function(e){return String(e).replace(/&amp;/g,"&").replace(/&quot;/g,'"').replace(/&lt;/g,"<").replace(/&gt;/g,">")},shortUnit:function(e){var i="";return e>=1e6?(e/=1e6,e=e>10?Math.floor(e):t.round(e,-1),i="M"):e>=1e3&&(e/=1e3,e=e>10?Math.floor(e):t.round(e,-1),i="k"),this.formatNumeric(e)+i}}}),define("WoltLabSuite/Core/Template",["./Template.grammar","./StringUtil","Language"],function(e,t,i){"use strict";function n(){this.yy={}}function a(n){void 0===i&&(i=require("Language")),void 0===t&&(t=require("StringUtil"));try{n=e.parse(n),n="var tmp = {};\nfor (var key in v) tmp[key] = v[key];\nv = tmp;\nv.__wcf = window.WCF; v.__window = window;\nreturn "+n,this.fetch=new Function("StringUtil","Language","v",n).bind(void 0,t,i)}catch(e){throw console.debug(e.message),e}}return n.prototype=e,e.Parser=n,e=new n,Object.defineProperty(a,"callbacks",{enumerable:!1,configurable:!1,get:function(){throw new Error("WCF.Template.callbacks is no longer supported")},set:function(e){throw new Error("WCF.Template.callbacks is no longer supported")}}),a.prototype={fetch:function(e){throw new Error("This Template is not initialized.")}},a}),define("WoltLabSuite/Core/Language",["Dictionary","./Template"],function(e,t){"use strict";var i=new e;return{addObject:function(t){i.merge(e.fromObject(t))},add:function(e,t){i.set(e,t)},get:function(e,n){n||(n={});var a=i.get(e);if(void 0===a)return e;if(void 0===t&&(t=require("WoltLabSuite/Core/Template")),"string"==typeof a){try{i.set(e,new t(a))}catch(n){i.set(e,new t("{literal}"+a.replace(/\{\/literal\}/g,"{/literal}{ldelim}/literal}{literal}")+"{/literal}"))}a=i.get(e)}return a instanceof t&&(a=a.fetch(n)),a}}}),define("WoltLabSuite/Core/CallbackList",["Dictionary"],function(e){"use strict";function t(){this._dictionary=new e}return t.prototype={add:function(e,t){if("function"!=typeof t)throw new TypeError("Expected a valid callback as second argument for identifier '"+e+"'.");this._dictionary.has(e)||this._dictionary.set(e,[]),this._dictionary.get(e).push(t)},remove:function(e){this._dictionary.delete(e)},forEach:function(e,t){if(null===e)this._dictionary.forEach(function(e,i){e.forEach(t)});else{var i=this._dictionary.get(e);void 0!==i&&i.forEach(t)}}},t}),define("WoltLabSuite/Core/Dom/Change/Listener",["CallbackList"],function(e){"use strict";var t=new e,i=!1;return{add:t.add.bind(t),remove:t.remove.bind(t),trigger:function(){if(!i)try{i=!0,t.forEach(null,function(e){e()})}finally{i=!1}}}}),define("WoltLabSuite/Core/Environment",[],function(){"use strict";var e="other",t="none",i="desktop",n=!1;return{setup:function(){if("object"==typeof window.chrome)e="chrome";else for(var a=window.getComputedStyle(document.documentElement),r=0,o=a.length;r<o;r++){var s=a[r];0===s.indexOf("-ms-")?e="microsoft":0===s.indexOf("-moz-")?e="firefox":"firefox"!==e&&0===s.indexOf("-webkit-")&&(e="safari")}var l=window.navigator.userAgent.toLowerCase();-1!==l.indexOf("crios")?(e="chrome",i="ios"):/(?:iphone|ipad|ipod)/.test(l)?(e="safari",i="ios"):-1!==l.indexOf("android")?i="android":-1!==l.indexOf("iemobile")&&(e="microsoft",i="windows"),"desktop"!==i||-1===l.indexOf("mobile")&&-1===l.indexOf("tablet")||(i="mobile"),t="redactor",n=!!("ontouchstart"in window)||!!("msMaxTouchPoints"in window.navigator)&&window.navigator.msMaxTouchPoints>0||window.DocumentTouch&&document instanceof DocumentTouch},browser:function(){return e},editor:function(){return t},platform:function(){return i},touch:function(){return n}}}),define("WoltLabSuite/Core/Dom/Util",["Environment","StringUtil"],function(e,t){"use strict";function i(e,t,i){if(!t.contains(e))throw new Error("Ancestor element does not contain target element.");for(var n,a=i+"Sibling";null!==e&&e!==t;){if(null!==e[i+"ElementSibling"])return!1;if(e[a])for(n=e[a];n;){if(""!==n.textContent.trim())return!1;n=n[a]}e=e.parentNode}return!0}var n=0,a={createFragmentFromHtml:function(e){var t=elCreate("div");this.setInnerHtml(t,e);for(var i=document.createDocumentFragment();t.childNodes.length;)i.appendChild(t.childNodes[0]);return i},getUniqueId:function(){var e;do{e="wcf"+n++}while(null!==elById(e));return e},identify:function(e){if(!(e instanceof Element))throw new TypeError("Expected a valid DOM element as argument.");var t=elAttr(e,"id");return t||(t=this.getUniqueId(),elAttr(e,"id",t)),t},outerHeight:function(e,t){t=t||window.getComputedStyle(e);var i=e.offsetHeight;return i+=~~t.marginTop+~~t.marginBottom},outerWidth:function(e,t){t=t||window.getComputedStyle(e);var i=e.offsetWidth;return i+=~~t.marginLeft+~~t.marginRight},outerDimensions:function(e){var t=window.getComputedStyle(e);return{height:this.outerHeight(e,t),width:this.outerWidth(e,t)}},offset:function(e){var t=e.getBoundingClientRect();return{top:Math.round(t.top+(window.scrollY||window.pageYOffset)),left:Math.round(t.left+(window.scrollX||window.pageXOffset))}},prepend:function(e,t){0===t.childNodes.length?t.appendChild(e):t.insertBefore(e,t.childNodes[0])},insertAfter:function(e,t){null!==t.nextSibling?t.parentNode.insertBefore(e,t.nextSibling):t.parentNode.appendChild(e)},setStyles:function(e,t){var i=!1;for(var n in t)t.hasOwnProperty(n)&&(/ !important$/.test(t[n])?(i=!0,t[n]=t[n].replace(/ !important$/,"")):i=!1,"important"!==e.style.getPropertyPriority(n)||i||e.style.removeProperty(n),e.style.setProperty(n,t[n],i?"important":""))},styleAsInt:function(e,t){var i=e.getPropertyValue(t);return null===i?0:parseInt(i)},setInnerHtml:function(e,t){e.innerHTML=t;for(var i,n,a=elBySelAll("script",e),r=0,o=a.length;r<o;r++)n=a[r],i=elCreate("script"),n.src?i.src=n.src:i.textContent=n.textContent,e.appendChild(i),elRemove(n)},insertHtml:function(e,t,i){var n=elCreate("div");if(this.setInnerHtml(n,e),n.childNodes.length){var a=n.childNodes[0];switch(i){case"append":t.appendChild(a);break;case"after":this.insertAfter(a,t);break;case"prepend":this.prepend(a,t);break;case"before":t.parentNode.insertBefore(a,t);break;default:throw new Error("Unknown insert method '"+i+"'.")}for(var r;n.childNodes.length;)r=n.childNodes[0],this.insertAfter(r,a),a=r}},contains:function(e,t){for(;null!==t;)if(t=t.parentNode,e===t)return!0;return!1},getDataAttributes:function(e,i,n,a){i=i||"",/^data-/.test(i)||(i="data-"+i),n=!0===n,a=!0===a;for(var r,o,s,l={},c=0,d=e.attributes.length;c<d;c++)if(r=e.attributes[c],0===r.name.indexOf(i)){if(o=r.name.replace(new RegExp("^"+i),""),n){s=o.split("-"),o="";for(var u=0,h=s.length;u<h;u++)o.length&&(a&&"id"===s[u]?s[u]="ID":s[u]=t.ucfirst(s[u])),o+=s[u]}l[o]=r.value}return l},unwrapChildNodes:function(e){for(var t=e.parentNode;e.childNodes.length;)t.insertBefore(e.childNodes[0],e);elRemove(e)},replaceElement:function(e,t){for(;e.childNodes.length;)t.appendChild(e.childNodes[0]);e.parentNode.insertBefore(t,e),elRemove(e)},isAtNodeStart:function(e,t){return i(e,t,"previous")},isAtNodeEnd:function(e,t){return i(e,t,"next")},getFixedParent:function(e){for(;e&&e!==document.body;){if("fixed"===window.getComputedStyle(e).getPropertyValue("position"))return e;e=e.offsetParent}return null}};return window.bc_wcfDomUtil=a,a}),define("WoltLabSuite/Core/ObjectMap",[],function(){"use strict";function e(){this._map=t?new WeakMap:{key:[],value:[]}}var t=objOwns(window,"WeakMap")&&"function"==typeof window.WeakMap;return e.prototype={set:function(e,i){if("object"!=typeof e||null===e)throw new TypeError("Only objects can be used as key");if("object"!=typeof i||null===i)throw new TypeError("Only objects can be used as value");t?this._map.set(e,i):(this._map.key.push(e),this._map.value.push(i))},delete:function(e){if(t)this._map.delete(e);else{var i=this._map.key.indexOf(e);this._map.key.splice(i),this._map.value.splice(i)}},has:function(e){return t?this._map.has(e):-1!==this._map.key.indexOf(e)},get:function(e){if(t)return this._map.get(e);var i=this._map.key.indexOf(e);return-1!==i?this._map.value[i]:void 0}},e}),define("WoltLabSuite/Core/Dom/Traverse",[],function(){"use strict";var e=[function(e,t){return!0},function(e,t){return e.matches(t)},function(e,t){return e.classList.contains(t)},function(e,t){return e.nodeName===t}],t=function(t,i,n){if(!(t instanceof Element))throw new TypeError("Expected a valid element as first argument.");for(var a=[],r=0;r<t.childElementCount;r++)e[i](t.children[r],n)&&a.push(t.children[r]);return a},i=function(t,i,n,a){if(!(t instanceof Element))throw new TypeError("Expected a valid element as first argument.");for(t=t.parentNode;t instanceof Element;){if(t===a)return null;if(e[i](t,n))return t;t=t.parentNode}return null},n=function(t,i,n,a){if(!(t instanceof Element))throw new TypeError("Expected a valid element as first argument.");return t instanceof Element&&null!==t[i]&&e[n](t[i],a)?t[i]:null};return{childBySel:function(e,i){return t(e,1,i)[0]||null},childByClass:function(e,i){return t(e,2,i)[0]||null},childByTag:function(e,i){return t(e,3,i)[0]||null},childrenBySel:function(e,i){return t(e,1,i)},childrenByClass:function(e,i){return t(e,2,i)},childrenByTag:function(e,i){return t(e,3,i)},parentBySel:function(e,t,n){return i(e,1,t,n)},parentByClass:function(e,t,n){return i(e,2,t,n)},parentByTag:function(e,t,n){return i(e,3,t,n)},next:function(e){return n(e,"nextElementSibling",0,null)},nextBySel:function(e,t){return n(e,"nextElementSibling",1,t)},nextByClass:function(e,t){return n(e,"nextElementSibling",2,t)},nextByTag:function(e,t){return n(e,"nextElementSibling",3,t)},prev:function(e){return n(e,"previousElementSibling",0,null)},prevBySel:function(e,t){return n(e,"previousElementSibling",1,t)},prevByClass:function(e,t){return n(e,"previousElementSibling",2,t)},prevByTag:function(e,t){return n(e,"previousElementSibling",3,t)}}}),define("WoltLabSuite/Core/Ui/Confirmation",["Core","Language","Ui/Dialog"],function(e,t,i){"use strict";var n=!1,a=null,r=null,o={},s=null;return{show:function(t){if(void 0===i&&(i=require("Ui/Dialog")),!n){if(o=e.extend({cancel:null,confirm:null,legacyCallback:null,message:"",messageIsHtml:!1,parameters:{},template:""},t),o.message="string"==typeof o.message?o.message.trim():"",!o.message.length)throw new Error("Expected a non-empty string for option 'message'.");if("function"!=typeof o.confirm&&"function"!=typeof o.legacyCallback)throw new TypeError("Expected a valid callback for option 'confirm'.");null===r&&this._createDialog(),r.innerHTML="string"==typeof o.template?o.template.trim():"",o.messageIsHtml?s.innerHTML=o.message:s.textContent=o.message,n=!0,i.open(this)}},_dialogSetup:function(){return{id:"wcfSystemConfirmation",options:{onClose:this._onClose.bind(this),onShow:this._onShow.bind(this),title:t.get("wcf.global.confirmation.title")}}},getContentElement:function(){return r},_createDialog:function(){var e=elCreate("div");elAttr(e,"id","wcfSystemConfirmation"),e.classList.add("systemConfirmation"),s=elCreate("p"),e.appendChild(s),r=elCreate("div"),elAttr(r,"id","wcfSystemConfirmationContent"),e.appendChild(r);var n=elCreate("div");n.classList.add("formSubmit"),e.appendChild(n),a=elCreate("button"),a.classList.add("buttonPrimary"),a.textContent=t.get("wcf.global.confirmation.confirm"),a.addEventListener(WCF_CLICK_EVENT,this._confirm.bind(this)),n.appendChild(a);var o=elCreate("button");o.textContent=t.get("wcf.global.confirmation.cancel"),o.addEventListener(WCF_CLICK_EVENT,function(){i.close("wcfSystemConfirmation")}),n.appendChild(o),document.body.appendChild(e)},_confirm:function(){"function"==typeof o.legacyCallback?o.legacyCallback("confirm",o.parameters,r):o.confirm(o.parameters,r),n=!1,i.close("wcfSystemConfirmation")},_onClose:function(){n&&(a.blur(),n=!1,"function"==typeof o.legacyCallback?o.legacyCallback("cancel",o.parameters,r):"function"==typeof o.cancel&&o.cancel(o.parameters))},_onShow:function(){a.blur(),a.focus()}}}),define("WoltLabSuite/Core/Ui/Screen",["Core","Dictionary","Environment"],function(e,t,i){"use strict";var n=null,a=new t,r=0,o=null,s=0,l=0,c=t.fromObject({"screen-xs":"(max-width: 544px)","screen-sm":"(min-width: 545px) and (max-width: 768px)","screen-sm-down":"(max-width: 768px)","screen-sm-up":"(min-width: 545px)","screen-sm-md":"(min-width: 545px) and (max-width: 1024px)","screen-md":"(min-width: 769px) and (max-width: 1024px)","screen-md-down":"(max-width: 1024px)","screen-md-up":"(min-width: 769px)","screen-lg":"(min-width: 1025px)"}),d=new t;return{on:function(t,i){var n=e.getUuid(),a=this._getQueryObject(t);return"function"==typeof i.match&&a.callbacksMatch.set(n,i.match),"function"==typeof i.unmatch&&a.callbacksUnmatch.set(n,i.unmatch),"function"==typeof i.setup&&(a.mql.matches?i.setup():a.callbacksSetup.set(n,i.setup)),n},remove:function(e,t){var i=this._getQueryObject(e);i.callbacksMatch.delete(t),i.callbacksUnmatch.delete(t),i.callbacksSetup.delete(t)},is:function(e){return this._getQueryObject(e).mql.matches},scrollDisable:function(){if(0===r){s=document.body.scrollTop,o="body",s||(s=document.documentElement.scrollTop,o="documentElement");var e=elById("pageContainer");"ios"===i.platform()?(e.style.setProperty("position","relative",""),e.style.setProperty("top","-"+s+"px","")):e.style.setProperty("margin-top","-"+s+"px",""),document.documentElement.classList.add("disableScrolling")}r++},scrollEnable:function(){if(r&&0===--r){document.documentElement.classList.remove("disableScrolling");var e=elById("pageContainer");"ios"===i.platform()?(e.style.removeProperty("position"),e.style.removeProperty("top")):e.style.removeProperty("margin-top"),s&&(document[o].scrollTop=~~s)}},pageOverlayOpen:function(){0===l&&document.documentElement.classList.add("pageOverlayActive"),l++},pageOverlayClose:function(){l&&0===--l&&document.documentElement.classList.remove("pageOverlayActive")},pageOverlayIsActive:function(){return l>0},setDialogContainer:function(e){n=e},_getQueryObject:function(e){if("string"!=typeof e||""===e.trim())throw new TypeError("Expected a non-empty string for parameter 'query'.");d.has(e)&&(e=d.get(e)),c.has(e)&&(e=c.get(e));var i=a.get(e);return i||(i={callbacksMatch:new t,callbacksUnmatch:new t,callbacksSetup:new t,mql:window.matchMedia(e)},i.mql.addListener(this._mqlChange.bind(this)),a.set(e,i),e!==i.mql.media&&d.set(i.mql.media,e)),i},_mqlChange:function(e){var i=this._getQueryObject(e.media);e.matches?i.callbacksSetup.size?(i.callbacksSetup.forEach(function(e){e()}),i.callbacksSetup=new t):i.callbacksMatch.forEach(function(e){e()}):i.callbacksUnmatch.forEach(function(e){e()})}}}),define("WoltLabSuite/Core/Event/Key",[],function(){"use strict";function e(e,t,i){if(!(e instanceof Event))throw new TypeError("Expected a valid event when testing for key '"+t+"'.");return e.key===t||e.which===i}return{ArrowDown:function(t){return e(t,"ArrowDown",40)},ArrowLeft:function(t){return e(t,"ArrowLeft",37)},ArrowRight:function(t){return e(t,"ArrowRight",39)},ArrowUp:function(t){return e(t,"ArrowUp",38)},Comma:function(t){return e(t,",",44)},End:function(t){return e(t,"End",35)},Enter:function(t){return e(t,"Enter",13)},Escape:function(t){return e(t,"Escape",27)},Home:function(t){return e(t,"Home",36)},Space:function(t){return e(t,"Space",32)},Tab:function(t){return e(t,"Tab",9)}}}),define("WoltLabSuite/Core/Ui/Alignment",["Core","Language","Dom/Traverse","Dom/Util"],function(e,t,i,n){"use strict";return{set:function(a,r,o){o=e.extend({verticalOffset:0,pointer:!1,pointerOffset:4,pointerClassNames:[],refDimensionsElement:null,horizontal:"left",vertical:"bottom",allowFlip:"both"},o),Array.isArray(o.pointerClassNames)&&o.pointerClassNames.length===(o.pointer?1:2)||(o.pointerClassNames=[]),-1===["left","right","center"].indexOf(o.horizontal)&&(o.horizontal="left"),"bottom"!==o.vertical&&(o.vertical="top"),-1===["both","horizontal","vertical","none"].indexOf(o.allowFlip)&&(o.allowFlip="both"),n.setStyles(a,{bottom:"auto !important",left:"0 !important",right:"auto !important",top:"0 !important",visibility:"hidden !important"});var s=n.outerDimensions(a),l=n.outerDimensions(o.refDimensionsElement instanceof Element?o.refDimensionsElement:r),c=n.offset(r),d=window.innerHeight,u=document.body.clientWidth,h={result:null},p=!1;if("center"===o.horizontal&&(p=!0,h=this._tryAlignmentHorizontal(o.horizontal,s,l,c,u),h.result||("both"===o.allowFlip||"horizontal"===o.allowFlip?o.horizontal="left":h.result=!0)),"rtl"===t.get("wcf.global.pageDirection")&&(o.horizontal="left"===o.horizontal?"right":"left"),!h.result){var f=h;if(h=this._tryAlignmentHorizontal(o.horizontal,s,l,c,u),!h.result&&("both"===o.allowFlip||"horizontal"===o.allowFlip)){var m=this._tryAlignmentHorizontal("left"===o.horizontal?"right":"left",s,l,c,u);m.result?h=m:p&&(h=f)}}var g=h.left,v=h.right,_=this._tryAlignmentVertical(o.vertical,s,l,c,d,o.verticalOffset);if(!_.result&&("both"===o.allowFlip||"vertical"===o.allowFlip)){var b=this._tryAlignmentVertical("top"===o.vertical?"bottom":"top",s,l,c,d,o.verticalOffset);b.result&&(_=b)}var w=_.bottom,y=_.top;if(o.pointer){var C=i.childrenByClass(a,"elementPointer");if(null===(C=C[0]||null))throw new Error("Expected the .elementPointer element to be a direct children.");"center"===h.align?(C.classList.add("center"),C.classList.remove("left"),C.classList.remove("right")):(C.classList.add(h.align),C.classList.remove("center"),C.classList.remove("left"===h.align?"right":"left")),"top"===_.align?C.classList.add("flipVertical"):C.classList.remove("flipVertical")}else if(2===o.pointerClassNames.length){a.classList["auto"===y?"add":"remove"](o.pointerClassNames[0]),a.classList["auto"===g?"add":"remove"](o.pointerClassNames[1])}"auto"!==w&&(w=Math.round(w)+"px"),"auto"!==g&&(g=Math.ceil(g)+"px"),"auto"!==v&&(v=Math.floor(v)+"px"),"auto"!==y&&(y=Math.round(y)+"px"),n.setStyles(a,{bottom:w,left:g,right:v,top:y}),elShow(a),a.style.removeProperty("visibility")},_tryAlignmentHorizontal:function(e,t,i,n,a){var r="auto",o="auto",s=!0;return"left"===e?(r=n.left)+t.width>a&&(s=!1):"right"===e?n.left+i.width<t.width?s=!1:(o=a-(n.left+i.width))<0&&(s=!1):(r=n.left+i.width/2-t.width/2,((r=~~r)<0||r+t.width>a)&&(s=!1)),{align:e,left:r,right:o,result:s}},_tryAlignmentVertical:function(e,t,i,n,a,r){var o="auto",s="auto",l=!0;if("top"===e){var c=document.body.clientHeight;o=c-n.top+r,c-(o+t.height)<(window.scrollY||window.pageYOffset)&&(l=!1)}else(s=n.top+i.height+r)+t.height-(window.scrollY||window.pageYOffset)>a&&(l=!1);return{align:e,bottom:o,top:s,result:l}}}}),define("WoltLabSuite/Core/Ui/CloseOverlay",["CallbackList"],function(e){"use strict";var t=new e,i={setup:function(){document.body.addEventListener(WCF_CLICK_EVENT,this.execute.bind(this))},add:t.add.bind(t),remove:t.remove.bind(t),execute:function(){t.forEach(null,function(e){e()})}};return i.setup(),i}),define("WoltLabSuite/Core/Ui/Dropdown/Simple",["CallbackList","Core","Dictionary","EventKey","Ui/Alignment","Dom/ChangeListener","Dom/Traverse","Dom/Util","Ui/CloseOverlay"],function(e,t,i,n,a,r,o,s,l){"use strict";var c=null,d=new e,u=!1,h=new i,p=new i,f=null,m=null,g="";return{setup:function(){u||(u=!0,f=elCreate("div"),f.className="dropdownMenuContainer",document.body.appendChild(f),c=elByClass("dropdownToggle"),this.initAll(),l.add("WoltLabSuite/Core/Ui/Dropdown/Simple",this.closeAll.bind(this)),r.add("WoltLabSuite/Core/Ui/Dropdown/Simple",this.initAll.bind(this)),document.addEventListener("scroll",this._onScroll.bind(this)),window.bc_wcfSimpleDropdown=this,m=this._dropdownMenuKeyDown.bind(this))},initAll:function(){for(var e=0,t=c.length;e<t;e++)this.init(c[e],!1)},init:function(e,i){if(this.setup(),elAttr(e,"role","button"),elAttr(e,"tabindex","0"),elAttr(e,"aria-haspopup",!0),elAttr(e,"aria-expanded",!1),e.classList.contains("jsDropdownEnabled")||elData(e,"target"))return!1;var n=o.parentByClass(e,"dropdown");if(null===n)throw new Error("Invalid dropdown passed, button '"+s.identify(e)+"' does not have a parent with .dropdown.");var a=o.nextByClass(e,"dropdownMenu");if(null===a)throw new Error("Invalid dropdown passed, button '"+s.identify(e)+"' does not have a menu as next sibling.");f.appendChild(a);var r=s.identify(n);if(!h.has(r)&&(e.classList.add("jsDropdownEnabled"),e.addEventListener(WCF_CLICK_EVENT,this._toggle.bind(this)),e.addEventListener("keydown",this._handleKeyDown.bind(this)),h.set(r,n),p.set(r,a),r.match(/^wcf\d+$/)||elData(a,"source",r),a.childElementCount&&a.children[0].classList.contains("scrollableDropdownMenu"))){a=a.children[0],elData(a,"scroll-to-active",!0);var l=null,c=null;a.addEventListener("wheel",function(e){null===l&&(l=a.clientHeight),null===c&&(c=a.scrollHeight),e.deltaY<0&&0===a.scrollTop?e.preventDefault():e.deltaY>0&&a.scrollTop+l===c&&e.preventDefault()},{passive:!1})}elData(e,"target",r),i&&setTimeout(function(){elData(e,"dropdown-lazy-init",i instanceof MouseEvent),t.triggerEvent(e,WCF_CLICK_EVENT),setTimeout(function(){e.removeAttribute("data-dropdown-lazy-init")},10)},10)},initFragment:function(e,t){this.setup();var i=s.identify(e);h.has(i)||(h.set(i,e),f.appendChild(t),p.set(i,t))},registerCallback:function(e,t){d.add(e,t)},getDropdown:function(e){return h.get(e)},getDropdownMenu:function(e){return p.get(e)},toggleDropdown:function(e,t,i){this._toggle(null,e,t,i)},setAlignment:function(e,t,i){var n,r=elBySel(".dropdownToggle",e);null!==r&&r.parentNode.classList.contains("inputAddonTextarea")&&(n=r),a.set(t,i||e,{pointerClassNames:["dropdownArrowBottom","dropdownArrowRight"],refDimensionsElement:n||null,horizontal:"right"===elData(t,"dropdown-alignment-horizontal")?"right":"left",vertical:"top"===elData(t,"dropdown-alignment-vertical")?"top":"bottom",allowFlip:elData(t,"dropdown-allow-flip")||"both"})},setAlignmentById:function(e){var t=h.get(e);if(void 0===t)throw new Error("Unknown dropdown identifier '"+e+"'.");var i=p.get(e);this.setAlignment(t,i)},isOpen:function(e){var t=p.get(e);return void 0!==t&&t.classList.contains("dropdownOpen")},open:function(e,t){var i=p.get(e);void 0===i||i.classList.contains("dropdownOpen")||this.toggleDropdown(e,void 0,t)},close:function(e){var t=h.get(e);void 0!==t&&(t.classList.remove("dropdownOpen"),p.get(e).classList.remove("dropdownOpen"))},closeAll:function(){h.forEach(function(e,t){e.classList.contains("dropdownOpen")&&(e.classList.remove("dropdownOpen"),p.get(t).classList.remove("dropdownOpen"),this._notifyCallbacks(t,"close"))}.bind(this))},destroy:function(e){if(!h.has(e))return!1;try{this.close(e),elRemove(p.get(e))}catch(e){}return p.delete(e),h.delete(e),!0},_onDialogScroll:function(e){for(var t=e.currentTarget,i=elBySelAll(".dropdown.dropdownOpen",t),n=0,a=i.length;n<a;n++){var r=i[n],o=s.identify(r),l=s.offset(r),c=s.offset(t);l.top+r.clientHeight<=c.top?this.toggleDropdown(o):l.top>=c.top+t.offsetHeight?this.toggleDropdown(o):l.left<=c.left?this.toggleDropdown(o):l.left>=c.left+t.offsetWidth?this.toggleDropdown(o):this.setAlignment(h.get(o),p.get(o))}},_onScroll:function(){h.forEach(function(e,t){if(e.classList.contains("dropdownOpen"))if(elDataBool(e,"is-overlay-dropdown-button"))this.setAlignment(e,p.get(t));else{var i=p.get(e.id);elDataBool(i,"dropdown-ignore-page-scroll")||this.close(t)}}.bind(this))},_notifyCallbacks:function(e,t){d.forEach(e,function(i){i(e,t)})},_toggle:function(e,t,i,n){null!==e&&(e.preventDefault(),e.stopPropagation(),t=elData(e.currentTarget,"target"),void 0===n&&e instanceof MouseEvent&&(n=!0));var a=h.get(t),r=!1;if(void 0!==a){var s;if(e&&(s=e.currentTarget,parent=s.parentNode,parent!==a&&(parent.classList.add("dropdown"),parent.id=a.id,a.classList.remove("dropdown"),a.id="",a=parent,h.set(t,parent))),void 0===n&&(s=a.closest(".dropdownToggle"),s||!(s=elBySel(".dropdownToggle",a))&&a.id&&(s=elBySel('[data-target="'+a.id+'"]')),s&&elDataBool(s,"dropdown-lazy-init")&&(n=!0)),elDataBool(a,"dropdown-prevent-toggle")&&a.classList.contains("dropdownOpen")&&(r=!0),""===elData(a,"is-overlay-dropdown-button")){var l=o.parentByClass(a,"dialogContent");elData(a,"is-overlay-dropdown-button",null!==l),null!==l&&l.addEventListener("scroll",this._onDialogScroll.bind(this))}}return g="",h.forEach(function(e,a){var o=p.get(a);if(e.classList.contains("dropdownOpen"))if(!1===r){e.classList.remove("dropdownOpen"),o.classList.remove("dropdownOpen");var s=elBySel(".dropdownToggle",e);s&&elAttr(s,"aria-expanded",!1),this._notifyCallbacks(a,"close")}else g=t;else if(a===t&&o.childElementCount>0){g=t,e.classList.add("dropdownOpen"),o.classList.add("dropdownOpen");var s=elBySel(".dropdownToggle",e);if(s&&elAttr(s,"aria-expanded",!0),o.childElementCount&&elDataBool(o.children[0],"scroll-to-active")){var l=o.children[0];l.removeAttribute("data-scroll-to-active");for(var c=null,d=0,u=l.childElementCount;d<u;d++)if(l.children[d].classList.contains("active")){c=l.children[d];break}c&&(l.scrollTop=Math.max(c.offsetTop+c.clientHeight-o.clientHeight,0))}var h=elBySel(".scrollableDropdownMenu",o);null!==h&&h.classList[h.scrollHeight>h.clientHeight?"add":"remove"]("forceScrollbar"),this._notifyCallbacks(a,"open");var f=null;n||(elAttr(o,"role","menu"),elAttr(o,"tabindex",-1),o.removeEventListener("keydown",m),o.addEventListener("keydown",m),elBySelAll("li",o,function(e){e.clientHeight&&(null===f?f=e:e.classList.contains("active")&&(f=e),elAttr(e,"role","menuitem"),elAttr(e,"tabindex",-1))})),this.setAlignment(e,o,i),null!==f&&f.focus()}}.bind(this)),window.WCF.Dropdown.Interactive.Handler.closeAll(),null===e},_handleKeyDown:function(e){(n.Enter(e)||n.Space(e))&&(e.preventDefault(),this._toggle(e))},_dropdownMenuKeyDown:function(e){var t,i,a=document.activeElement;if("LI"===a.nodeName)if(n.ArrowDown(e)||n.ArrowUp(e)||n.End(e)||n.Home(e)){e.preventDefault();var r=Array.prototype.slice.call(elBySelAll("li",a.closest(".dropdownMenu")));(n.ArrowUp(e)||n.End(e))&&r.reverse();var o=null,s=function(e){return!e.classList.contains("dropdownDivider")&&e.clientHeight>0},l=r.indexOf(a);(n.End(e)||n.Home(e))&&(l=-1);for(var c=l+1;c<r.length;c++)if(s(r[c])){o=r[c];break}if(null===o)for(c=0;c<r.length;c++)if(s(r[c])){o=r[c];break}o.focus()}else if(n.Enter(e)||n.Space(e)){e.preventDefault();var d=a;1!==d.childElementCount||"SPAN"!==d.children[0].nodeName&&"A"!==d.children[0].nodeName||(d=d.children[0]),i=h.get(g),t=elBySel(".dropdownToggle",i),require(["Core"],function(e){var n=elData(i,"a11y-mouse-event")||"click";e.triggerEvent(d,n),t&&t.focus()})}else(n.Escape(e)||n.Tab(e))&&(e.preventDefault(),i=h.get(g),t=elBySel(".dropdownToggle",i),null!==t||i.classList.contains("dropdown")||(t=i),this._toggle(null,g),t&&t.focus())}}}),define("WoltLabSuite/Core/Devtools",[],function(){"use strict";var e={editorAutosave:!0,eventLogging:!1},t=function(){window.sessionStorage&&window.sessionStorage.setItem("__wsc_devtools_config",JSON.stringify(e))},i={help:function(){window.console.log(""),window.console.log("%cAvailable commands:","text-decoration: underline");var e=[];for(var t in i)"_internal_"!==t&&i.hasOwnProperty(t)&&e.push(t);e.sort().forEach(function(e){window.console.log("\tDevtools."+e+"()")}),window.console.log("")},toggleEditorAutosave:function(i){e.editorAutosave=!0!==i&&!e.editorAutosave,t(),window.console.log("%c\tEditor autosave "+(e.editorAutosave?"enabled":"disabled"),"font-style: italic")},toggleEventLogging:function(i){e.eventLogging=!0===i||!e.eventLogging,t(),window.console.log("%c\tEvent logging "+(e.eventLogging?"enabled":"disabled"),"font-style: italic")},_internal_:{enable:function(){if(window.Devtools=i,window.console.log("%cDevtools for WoltLab Suite loaded","font-weight: bold"),window.sessionStorage){var t=window.sessionStorage.getItem("__wsc_devtools_config");try{null!==t&&(e=JSON.parse(t))}catch(e){}e.editorAutosave||i.toggleEditorAutosave(!0),e.eventLogging&&i.toggleEventLogging(!0)}window.console.log("Settings are saved per browser session, enter `Devtools.help()` to learn more."),window.console.log("")},editorAutosave:function(){return e.editorAutosave},eventLog:function(t,i){e.eventLogging&&window.console.log("[Devtools.EventLogging] Firing event: "+i+" @ "+t)}}};return i}),define("WoltLabSuite/Core/Event/Handler",["Core","Devtools","Dictionary"],function(e,t,i){"use strict";var n=new i;return{add:function(t,a,r){if("function"!=typeof r)throw new TypeError("[WoltLabSuite/Core/Event/Handler] Expected a valid callback for '"+a+"@"+t+"'.");var o=n.get(t);void 0===o&&(o=new i,n.set(t,o));var s=o.get(a);void 0===s&&(s=new i,o.set(a,s));var l=e.getUuid();return s.set(l,r),l},fire:function(e,i,a){t._internal_.eventLog(e,i),a=a||{};var r=n.get(e);if(void 0!==r){var o=r.get(i);void 0!==o&&o.forEach(function(e){e(a)})}},remove:function(e,t,i){var a=n.get(e);if(void 0!==a){var r=a.get(t)
+;void 0!==r&&r.delete(i)}},removeAll:function(e,t){"string"!=typeof t&&(t=void 0);var i=n.get(e);void 0!==i&&(void 0===t?n.delete(e):i.delete(t))},removeAllBySuffix:function(e,t){var i=n.get(e);if(void 0!==i){t="_"+t;var a=-1*t.length;i.forEach(function(i,n){n.substr(a)===t&&this.removeAll(e,n)}.bind(this))}}}}),define("WoltLabSuite/Core/List",[],function(){"use strict";function e(){this._set=t?new Set:[]}var t=objOwns(window,"Set")&&"function"==typeof window.Set;return e.prototype={add:function(e){t?this._set.add(e):this.has(e)||this._set.push(e)},clear:function(){t?this._set.clear():this._set=[]},delete:function(e){if(t)return this._set.delete(e);var i=this._set.indexOf(e);return-1!==i&&(this._set.splice(i,1),!0)},forEach:function(e){if(t)this._set.forEach(e);else for(var i=0,n=this._set.length;i<n;i++)e(this._set[i])},has:function(e){return t?this._set.has(e):-1!==this._set.indexOf(e)}},Object.defineProperty(e.prototype,"size",{enumerable:!1,configurable:!0,get:function(){return t?this._set.size:this._set.length}}),e}),define("WoltLabSuite/Core/Ui/Dialog",["Ajax","Core","Dictionary","Environment","Language","ObjectMap","Dom/ChangeListener","Dom/Traverse","Dom/Util","Ui/Confirmation","Ui/Screen","Ui/SimpleDropdown","EventHandler","List","EventKey"],function(e,t,i,n,a,r,o,s,l,c,d,u,h,p,f){"use strict";var m=null,g=null,v=null,_=new i,b=!1,w=new r,y=new i,C=null,E=null,L=elByClass("jsStaticDialog"),I=["onBeforeClose","onClose","onShow"],S=["number","password","search","tel","text","url"],A=['a[href]:not([tabindex^="-"]):not([inert])','area[href]:not([tabindex^="-"]):not([inert])',"input:not([disabled]):not([inert])","select:not([disabled]):not([inert])","textarea:not([disabled]):not([inert])","button:not([disabled]):not([inert])",'iframe:not([tabindex^="-"]):not([inert])','audio:not([tabindex^="-"]):not([inert])','video:not([tabindex^="-"]):not([inert])','[contenteditable]:not([tabindex^="-"]):not([inert])','[tabindex]:not([tabindex^="-"]):not([inert])'];return{setup:function(){void 0===e&&(e=require("Ajax")),v=elCreate("div"),v.classList.add("dialogOverlay"),elAttr(v,"aria-hidden","true"),v.addEventListener("mousedown",this._closeOnBackdrop.bind(this)),v.addEventListener("wheel",function(e){e.target===v&&e.preventDefault()},{passive:!1}),elById("content").appendChild(v),E=function(e){return 27!==e.keyCode||"INPUT"===e.target.nodeName||"TEXTAREA"===e.target.nodeName||(this.close(m),!1)}.bind(this),d.on("screen-xs",{match:function(){b=!0},unmatch:function(){b=!1},setup:function(){b=!0}}),this._initStaticDialogs(),o.add("Ui/Dialog",this._initStaticDialogs.bind(this)),d.setDialogContainer(v),"ios"===n.platform()&&window.addEventListener("resize",function(){_.forEach(function(e){elAttrBool(e.dialog,"aria-hidden")||this.rebuild(elData(e.dialog,"id"))}.bind(this))}.bind(this))},_initStaticDialogs:function(){for(var e,t,i;L.length;)e=L[0],e.classList.remove("jsStaticDialog"),(i=elData(e,"dialog-id"))&&(t=elById(i))&&function(e,t){t.classList.remove("jsStaticDialogContent"),elData(t,"is-static-dialog",!0),elHide(t),e.addEventListener(WCF_CLICK_EVENT,function(e){e.preventDefault(),this.openStatic(t.id,null,{title:elData(t,"title")})}.bind(this))}.bind(this)(e,t)},open:function(i,n){var a=w.get(i);if(t.isPlainObject(a))return this.openStatic(a.id,n);if("function"!=typeof i._dialogSetup)throw new Error("Callback object does not implement the method '_dialogSetup()'.");var r=i._dialogSetup();if(!t.isPlainObject(r))throw new Error("Expected an object literal as return value of '_dialogSetup()'.");a={id:r.id};var o=!0;if(void 0===r.source){var s=elById(r.id);if(null===s)throw new Error("Element id '"+r.id+"' is invalid and no source attribute was given. If you want to use the `html` argument instead, please add `source: null` to your dialog configuration.");r.source=document.createDocumentFragment(),r.source.appendChild(s),s.removeAttribute("id"),elShow(s)}else if(null===r.source)r.source=n;else if("function"==typeof r.source)r.source();else if(t.isPlainObject(r.source)){if("string"!=typeof n||""===n.trim())return e.api(this,r.source.data,function(e){e.returnValues&&"string"==typeof e.returnValues.template&&(this.open(i,e.returnValues.template),"function"==typeof r.source.after&&r.source.after(_.get(r.id).content,e))}.bind(this)),{};r.source=n}else{if("string"==typeof r.source){var s=elCreate("div");elAttr(s,"id",r.id),l.setInnerHtml(s,r.source),r.source=document.createDocumentFragment(),r.source.appendChild(s)}if(!r.source.nodeType||r.source.nodeType!==Node.DOCUMENT_FRAGMENT_NODE)throw new Error("Expected at least a document fragment as 'source' attribute.");o=!1}return w.set(i,a),y.set(r.id,i),this.openStatic(r.id,r.source,r.options,o)},openStatic:function(e,i,r,o){d.pageOverlayOpen(),"desktop"!==n.platform()&&(this.isOpen(e)||d.scrollDisable()),_.has(e)?this._updateDialog(e,i):(r=t.extend({backdropCloseOnClick:!0,closable:!0,closeButtonLabel:a.get("wcf.global.button.close"),closeConfirmMessage:"",disableContentPadding:!1,title:"",onBeforeClose:null,onClose:null,onShow:null},r),r.closable||(r.backdropCloseOnClick=!1),r.closeConfirmMessage&&(r.onBeforeClose=function(e){c.show({confirm:this.close.bind(this,e),message:r.closeConfirmMessage})}.bind(this)),this._createDialog(e,i,r));var s=_.get(e);return"ios"===n.platform()&&window.setTimeout(function(){var e=elBySel("input, textarea",s.content);null!==e&&e.focus()}.bind(this),200),s},setTitle:function(e,t){e=this._getDialogId(e);var i=_.get(e);if(void 0===i)throw new Error("Expected a valid dialog id, '"+e+"' does not match any active dialog.");var n=elByClass("dialogTitle",i.dialog);n.length&&(n[0].textContent=t)},setCallback:function(e,t,i){if("object"==typeof e){var n=w.get(e);void 0!==n&&(e=n.id)}var a=_.get(e);if(void 0===a)throw new Error("Expected a valid dialog id, '"+e+"' does not match any active dialog.");if(-1===I.indexOf(t))throw new Error("Invalid callback identifier, '"+t+"' is not recognized.");if("function"!=typeof i&&null!==i)throw new Error("Only functions or the 'null' value are acceptable callback values ('"+typeof i+"' given).");a[t]=i},_createDialog:function(e,t,i,n){var a=null;if(null===t&&null===(a=elById(e)))throw new Error("Expected either a HTML string or an existing element id.");var r=elCreate("div");r.classList.add("dialogContainer"),elAttr(r,"aria-hidden","true"),elAttr(r,"role","dialog"),elData(r,"id",e);var o=elCreate("header");r.appendChild(o);var s=l.getUniqueId();elAttr(r,"aria-labelledby",s);var c=elCreate("span");if(c.classList.add("dialogTitle"),c.textContent=i.title,elAttr(c,"id",s),o.appendChild(c),i.closable){var d=elCreate("a");d.className="dialogCloseButton jsTooltip",elAttr(d,"role","button"),elAttr(d,"tabindex","0"),elAttr(d,"title",i.closeButtonLabel),elAttr(d,"aria-label",i.closeButtonLabel),d.addEventListener(WCF_CLICK_EVENT,this._close.bind(this)),o.appendChild(d);var u=elCreate("span");u.className="icon icon24 fa-times",d.appendChild(u)}var h=elCreate("div");h.classList.add("dialogContent"),i.disableContentPadding&&h.classList.add("dialogContentNoPadding"),r.appendChild(h),h.addEventListener("wheel",function(e){for(var t,i,n,a=!1,r=e.target;;){if(t=r.clientHeight,i=r.scrollHeight,t<i){if(n=r.scrollTop,e.deltaY<0&&n>0){a=!0;break}if(e.deltaY>0&&n+t<i){a=!0;break}}if(!r||r===h)break;r=r.parentNode}!1===a&&e.preventDefault()},{passive:!1});var f;if(null===a)if("string"==typeof t)f=elCreate("div"),f.id=e,l.setInnerHtml(f,t);else{if(!(t instanceof DocumentFragment))throw new TypeError("'html' must either be a string or a DocumentFragment");for(var m,g=[],b=0,w=t.childNodes.length;b<w;b++)m=t.childNodes[b],m.nodeType===Node.ELEMENT_NODE&&g.push(m);"DIV"!==g[0].nodeName||g.length>1?(f=elCreate("div"),f.id=e,f.appendChild(t)):f=g[0]}else f=a;h.appendChild(f),"none"===f.style.getPropertyValue("display")&&elShow(f),_.set(e,{backdropCloseOnClick:i.backdropCloseOnClick,closable:i.closable,content:f,dialog:r,header:o,onBeforeClose:i.onBeforeClose,onClose:i.onClose,onShow:i.onShow,submitButton:null,inputFields:new p}),l.prepend(r,v),"function"==typeof i.onSetup&&i.onSetup(f),!0!==n&&this._updateDialog(e,null)},_updateDialog:function(e,t){var i=_.get(e);if(void 0===i)throw new Error("Expected a valid dialog id, '"+e+"' does not match any active dialog.");if("string"==typeof t&&l.setInnerHtml(i.content,t),"true"===elAttr(i.dialog,"aria-hidden")){null===g&&(g=this._maintainFocus.bind(this),document.body.addEventListener("focus",g,{capture:!0})),i.closable&&"true"===elAttr(v,"aria-hidden")&&window.addEventListener("keyup",E),elAttr(i.dialog,"aria-hidden","false"),elAttr(v,"aria-hidden","false"),elData(v,"close-on-click",i.backdropCloseOnClick?"true":"false"),m=e,C=document.activeElement;var n=elBySel(".dialogCloseButton",i.header);n&&elAttr(n,"inert",!0),this._setFocusToFirstItem(i.dialog),n&&n.removeAttribute("inert"),"function"==typeof i.onShow&&i.onShow(i.content),elDataBool(i.content,"is-static-dialog")&&h.fire("com.woltlab.wcf.dialog","openStatic",{content:i.content,id:e}),u.closeAll(),window.WCF.Dropdown.Interactive.Handler.closeAll()}this.rebuild(e),o.trigger()},_maintainFocus:function(e){if(m){var t=_.get(m);t.dialog.contains(e.target)||e.target.closest(".dropdownMenuContainer")||e.target.closest(".datePicker")||this._setFocusToFirstItem(t.dialog,!0)}},_setFocusToFirstItem:function(e,t){var i=this._getFirstFocusableChild(e);null!==i&&(t&&("username"!==i.id&&"username"!==i.name||"safari"===n.browser()&&"ios"===n.platform()&&(i=null)),i&&i.focus())},_getFirstFocusableChild:function(e){for(var t=elBySelAll(A.join(","),e),i=0,n=t.length;i<n;i++)if(t[i].offsetWidth&&t[i].offsetHeight&&t[i].getClientRects().length)return t[i];return null},rebuild:function(e){e=this._getDialogId(e);var t=_.get(e);if(void 0===t)throw new Error("Expected a valid dialog id, '"+e+"' does not match any active dialog.");if("true"!==elAttr(t.dialog,"aria-hidden")){var i=t.content.parentNode,a=elBySel(".formSubmit",t.content),r=0;null!==a?(i.classList.add("dialogForm"),a.classList.add("dialogFormSubmit"),r+=l.outerHeight(a),r-=1,i.style.setProperty("margin-bottom",r+"px","")):(i.classList.remove("dialogForm"),i.style.removeProperty("margin-bottom")),r+=l.outerHeight(t.header);var o=window.innerHeight*(b?1:.8)-r;if(i.style.setProperty("max-height",~~o+"px",""),"chrome"===n.browser()||"safari"===n.browser()){var s=parseFloat(window.getComputedStyle(t.content).width),c=Math.round(s)%2!=0;t.content.parentNode.classList[c?"add":"remove"]("jsWebKitFractionalPixel")}var d=y.get(e);if(void 0!==d&&"function"==typeof d._dialogSubmit){var u=elBySelAll('input[data-dialog-submit-on-enter="true"]',t.content),h=elBySel('.formSubmit > input[type="submit"], .formSubmit > button[data-type="submit"]',t.content);if(null===h)return void(0===u.length&&console.warn("Broken dialog, expected a submit button.",t.content));if(t.submitButton!==h){t.submitButton=h,h.addEventListener(WCF_CLICK_EVENT,function(t){t.preventDefault(),this._submit(e)}.bind(this));for(var p,m=null,g=0,v=u.length;g<v;g++)p=u[g],t.inputFields.has(p)||(-1!==S.indexOf(p.type)?(t.inputFields.add(p),null===m&&(m=function(t){f.Enter(t)&&(t.preventDefault(),this._submit(e))}.bind(this)),p.addEventListener("keydown",m)):console.warn("Unsupported input type.",p))}}}},_submit:function(e){var t=_.get(e),i=!0;t.inputFields.forEach(function(e){e.required&&(""===e.value.trim()?(elInnerError(e,a.get("wcf.global.form.error.empty")),i=!1):elInnerError(e,!1))}),i&&y.get(e)._dialogSubmit()},_close:function(e){e.preventDefault();var t=_.get(m);if("function"==typeof t.onBeforeClose)return t.onBeforeClose(m),!1;this.close(m)},_closeOnBackdrop:function(e){if(e.target!==v)return!0;"true"===elData(v,"close-on-click")?this._close(e):e.preventDefault()},close:function(e){e=this._getDialogId(e);var t=_.get(e);if(void 0===t)throw new Error("Expected a valid dialog id, '"+e+"' does not match any active dialog.");elAttr(t.dialog,"aria-hidden","true"),document.activeElement.closest(".dialogContainer")===t.dialog&&document.activeElement.blur(),"function"==typeof t.onClose&&t.onClose(e),m=null;for(var i=0;i<v.childElementCount;i++){var a=v.children[i];if("false"===elAttr(a,"aria-hidden")){m=elData(a,"id");break}}null===m?(elAttr(v,"aria-hidden","true"),elData(v,"close-on-click","false"),t.closable&&window.removeEventListener("keyup",E),d.pageOverlayClose()):(t=_.get(m),elData(v,"close-on-click",t.backdropCloseOnClick?"true":"false")),"desktop"!==n.platform()&&d.scrollEnable()},getDialog:function(e){return _.get(this._getDialogId(e))},isOpen:function(e){var t=this.getDialog(e);return void 0!==t&&"false"===elAttr(t.dialog,"aria-hidden")},destroy:function(e){if("object"!=typeof e||e instanceof String)throw new TypeError("Expected the callback object as parameter.");if(w.has(e)){var t=w.get(e).id;this.isOpen(t)&&this.close(t),_.delete(t),w.delete(e)}},_getDialogId:function(e){if("object"==typeof e){var t=w.get(e);if(void 0!==t)return t.id}return e.toString()},_ajaxSetup:function(){return{}}}}),define("WoltLabSuite/Core/Ajax/Status",["Language"],function(e){"use strict";var t=0,i=null,n=null;return{_init:function(){i=elCreate("div"),i.classList.add("spinner"),elAttr(i,"role","status");var t=elCreate("span");t.className="icon icon48 fa-spinner",i.appendChild(t);var n=elCreate("span");n.textContent=e.get("wcf.global.loading"),i.appendChild(n),document.body.appendChild(i)},show:function(){null===i&&this._init(),t++,null===n&&(n=window.setTimeout(function(){t&&i.classList.add("active"),n=null},250))},hide:function(){0===--t&&(null!==n&&window.clearTimeout(n),i.classList.remove("active"))}}}),define("WoltLabSuite/Core/Ajax/Request",["Core","Language","Dom/ChangeListener","Dom/Util","Ui/Dialog","WoltLabSuite/Core/Ajax/Status"],function(e,t,i,n,a,r){"use strict";function o(e){this._data=null,this._options={},this._previousXhr=null,this._xhr=null,this._init(e)}var s=!1,l=!1;return o.prototype={_init:function(t){this._options=e.extend({data:{},contentType:"application/x-www-form-urlencoded; charset=UTF-8",responseType:"application/json",type:"POST",url:"",withCredentials:!1,autoAbort:!1,ignoreError:!1,pinData:!1,silent:!1,includeRequestedWith:!0,failure:null,finalize:null,success:null,progress:null,uploadProgress:null,callbackObject:null},t),"object"==typeof t.callbackObject&&(this._options.callbackObject=t.callbackObject),this._options.url=e.convertLegacyUrl(this._options.url),0===this._options.url.indexOf("index.php")&&(this._options.url=WSC_API_URL+this._options.url),0===this._options.url.indexOf(WSC_API_URL)&&(this._options.includeRequestedWith=!0,this._options.withCredentials=!0),this._options.pinData&&(this._data=e.extend({},this._options.data)),null!==this._options.callbackObject&&("function"==typeof this._options.callbackObject._ajaxFailure&&(this._options.failure=this._options.callbackObject._ajaxFailure.bind(this._options.callbackObject)),"function"==typeof this._options.callbackObject._ajaxFinalize&&(this._options.finalize=this._options.callbackObject._ajaxFinalize.bind(this._options.callbackObject)),"function"==typeof this._options.callbackObject._ajaxSuccess&&(this._options.success=this._options.callbackObject._ajaxSuccess.bind(this._options.callbackObject)),"function"==typeof this._options.callbackObject._ajaxProgress&&(this._options.progress=this._options.callbackObject._ajaxProgress.bind(this._options.callbackObject)),"function"==typeof this._options.callbackObject._ajaxUploadProgress&&(this._options.uploadProgress=this._options.callbackObject._ajaxUploadProgress.bind(this._options.callbackObject))),!1===s&&(s=!0,window.addEventListener("beforeunload",function(){l=!0}))},sendRequest:function(t){(!0===t||this._options.autoAbort)&&this.abortPrevious(),this._options.silent||r.show(),this._xhr instanceof XMLHttpRequest&&(this._previousXhr=this._xhr),this._xhr=new XMLHttpRequest,this._xhr.open(this._options.type,this._options.url,!0),this._options.contentType&&this._xhr.setRequestHeader("Content-Type",this._options.contentType),(this._options.withCredentials||this._options.includeRequestedWith)&&this._xhr.setRequestHeader("X-Requested-With","XMLHttpRequest"),this._options.withCredentials&&(this._xhr.withCredentials=!0);var i=this,n=e.clone(this._options);if(this._xhr.onload=function(){this.readyState===XMLHttpRequest.DONE&&(this.status>=200&&this.status<300||304===this.status?n.responseType&&0!==this.getResponseHeader("Content-Type").indexOf(n.responseType)?i._failure(this,n):i._success(this,n):i._failure(this,n))},this._xhr.onerror=function(){i._failure(this,n)},this._options.progress&&(this._xhr.onprogress=this._options.progress),this._options.uploadProgress&&(this._xhr.upload.onprogress=this._options.uploadProgress),"POST"===this._options.type){var a=this._options.data;"object"==typeof a&&"FormData"!==e.getType(a)&&(a=e.serialize(a)),this._xhr.send(a)}else this._xhr.send()},abortPrevious:function(){null!==this._previousXhr&&(this._previousXhr.abort(),this._previousXhr=null,this._options.silent||r.hide())},setOption:function(e,t){this._options[e]=t},getOption:function(e){return objOwns(this._options,e)?this._options[e]:null},setData:function(t){null!==this._data&&"FormData"!==e.getType(t)&&(t=e.extend(this._data,t)),this._options.data=t},_success:function(e,t){if(t.silent||r.hide(),"function"==typeof t.success){var i=null;if("application/json"===e.getResponseHeader("Content-Type")){try{i=JSON.parse(e.responseText)}catch(i){return void this._failure(e,t)}i&&i.returnValues&&void 0!==i.returnValues.template&&(i.returnValues.template=i.returnValues.template.trim()),i&&i.forceBackgroundQueuePerform&&require(["WoltLabSuite/Core/BackgroundQueue"],function(e){e.invoke()})}t.success(i,e.responseText,e,t.data)}this._finalize(t)},_failure:function(e,i){if(!l){i.silent||r.hide();var o=null;try{o=JSON.parse(e.responseText)}catch(e){}var s=!0;if("function"==typeof i.failure&&(s=i.failure(o||{},e.responseText||"",e,i.data)),!0!==i.ignoreError&&!1!==s){var c=this.getErrorHtml(o,e);c&&(void 0===a&&(a=require("Ui/Dialog")),a.openStatic(n.getUniqueId(),c,{title:t.get("wcf.global.error.title")}))}this._finalize(i)}},getErrorHtml:function(e,t){var i="",n="";if(null!==e?(e.file&&e.line&&(i+="<br><p>File:</p><p>"+e.file+" in line "+e.line+"</p>"),e.stacktrace?i+="<br><p>Stacktrace:</p><p>"+e.stacktrace+"</p>":e.exceptionID&&(i+="<br><p>Exception ID: <code>"+e.exceptionID+"</code></p>"),n=e.message,e.previous.forEach(function(e){i+="<hr><p>"+e.message+"</p>",i+="<br><p>Stacktrace</p><p>"+e.stacktrace+"</p>"})):n=t.responseText,!n||"undefined"===n){if(!ENABLE_DEBUG_MODE&&!ENABLE_PRODUCTION_DEBUG_MODE)return null;n="XMLHttpRequest failed without a responseText. Check your browser console."}return'<div class="ajaxDebugMessage"><p>'+n+"</p>"+i+"</div>"},_finalize:function(e){"function"==typeof e.finalize&&e.finalize(this._xhr),this._previousXhr=null,i.trigger();for(var t=elBySelAll('a[href*="#"]'),n=0,a=t.length;n<a;n++){var r=t[n],o=elAttr(r,"href");-1===o.indexOf("AJAXProxy")&&-1===o.indexOf("ajax-proxy")||(o=o.substr(o.indexOf("#")),elAttr(r,"href",document.location.toString().replace(/#.*/,"")+o))}}},o}),define("WoltLabSuite/Core/Ajax",["AjaxRequest","Core","ObjectMap"],function(e,t,i){"use strict";var n=new i;return{api:function(t,i,a,r){void 0===e&&(e=require("AjaxRequest")),"object"!=typeof i&&(i={});var o=n.get(t);if(void 0===o){if("function"!=typeof t._ajaxSetup)throw new TypeError("Callback object must implement at least _ajaxSetup().");var s=t._ajaxSetup();s.pinData=!0,s.callbackObject=t,s.url||(s.url="index.php?ajax-proxy/&t="+SECURITY_TOKEN,s.withCredentials=!0),o=new e(s),n.set(t,o)}var l=null,c=null;return"function"==typeof a&&(l=o.getOption("success"),o.setOption("success",a)),"function"==typeof r&&(c=o.getOption("failure"),o.setOption("failure",r)),o.setData(i),o.sendRequest(),null!==l&&o.setOption("success",l),null!==c&&o.setOption("failure",c),o},apiOnce:function(t){void 0===e&&(e=require("AjaxRequest")),t.pinData=!1,t.callbackObject=null,t.url||(t.url="index.php?ajax-proxy/&t="+SECURITY_TOKEN,t.withCredentials=!0),new e(t).sendRequest(!1)},getRequestObject:function(e){if(!n.has(e))throw new Error("Expected a previously used callback object, provided object is unknown.");return n.get(e)}}}),define("WoltLabSuite/Core/BackgroundQueue",["Ajax"],function(e){"use strict";var t=0,i=!1,n="";return{setUrl:function(e){n=e},invoke:function(){if(""===n)return void console.error("The background queue has not been initialized yet.");i||(i=!0,e.api(this))},_ajaxSuccess:function(e){t++,e>0&&t<5?window.setTimeout(function(){i=!1,this.invoke()}.bind(this),1e3):(i=!1,t=0)},_ajaxSetup:function(){return{url:n,ignoreError:!0,silent:!0}}}}),function(){var e=function(e){"use strict";function t(e){if(e.paused||e.ended||g)return!1;try{d.clearRect(0,0,l,s),d.drawImage(e,0,0,l,s)}catch(e){}b=setTimeout(function(){t(e)},N.duration),k.setIcon(c)}function i(e){var t=/^#?([a-f\d])([a-f\d])([a-f\d])$/i;e=e.replace(t,function(e,t,i,n){return t+t+i+i+n+n});var i=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e);return!!i&&{r:parseInt(i[1],16),g:parseInt(i[2],16),b:parseInt(i[3],16)}}function n(e,t){var i,n={};for(i in e)n[i]=e[i];for(i in t)n[i]=t[i];return n}function a(){return w.hidden||w.msHidden||w.webkitHidden||w.mozHidden}e=e||{};var r,o,s,l,c,d,u,h,p,f,m,g,v,_,b,w,y={bgColor:"#d00",textColor:"#fff",fontFamily:"sans-serif",fontStyle:"bold",type:"circle",position:"down",animation:"slide",elementId:!1,element:null,dataUrl:!1,win:window};v={},v.ff="undefined"!=typeof InstallTrigger,v.chrome=!!window.chrome,v.opera=!!window.opera||navigator.userAgent.indexOf("Opera")>=0,v.ie=!1,v.safari=Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor")>0,v.supported=v.chrome||v.ff||v.opera;var C=[];m=function(){},h=g=!1;var E={};E.ready=function(){h=!0,E.reset(),m()},E.reset=function(){h&&(C=[],p=!1,f=!1,d.clearRect(0,0,l,s),d.drawImage(u,0,0,l,s),k.setIcon(c),window.clearTimeout(_),window.clearTimeout(b))},E.start=function(){if(h&&!f){var e=function(){p=C[0],f=!1,C.length>0&&(C.shift(),E.start())};if(C.length>0){f=!0;var t=function(){["type","animation","bgColor","textColor","fontFamily","fontStyle"].forEach(function(e){e in C[0].options&&(r[e]=C[0].options[e])}),N.run(C[0].options,function(){e()},!1)};p?N.run(p.options,function(){t()},!0):t()}}};var L={},I=function(e){return e.n="number"==typeof e.n?Math.abs(0|e.n):e.n,e.x=l*e.x,e.y=s*e.y,e.w=l*e.w,e.h=s*e.h,e.len=(""+e.n).length,e};L.circle=function(e){e=I(e);var t=!1;2===e.len?(e.x=e.x-.4*e.w,e.w=1.4*e.w,t=!0):e.len>=3&&(e.x=e.x-.65*e.w,e.w=1.65*e.w,t=!0),d.clearRect(0,0,l,s),d.drawImage(u,0,0,l,s),d.beginPath(),d.font=r.fontStyle+" "+Math.floor(e.h*(e.n>99?.85:1))+"px "+r.fontFamily,d.textAlign="center",t?(d.moveTo(e.x+e.w/2,e.y),d.lineTo(e.x+e.w-e.h/2,e.y),d.quadraticCurveTo(e.x+e.w,e.y,e.x+e.w,e.y+e.h/2),d.lineTo(e.x+e.w,e.y+e.h-e.h/2),d.quadraticCurveTo(e.x+e.w,e.y+e.h,e.x+e.w-e.h/2,e.y+e.h),d.lineTo(e.x+e.h/2,e.y+e.h),d.quadraticCurveTo(e.x,e.y+e.h,e.x,e.y+e.h-e.h/2),d.lineTo(e.x,e.y+e.h/2),d.quadraticCurveTo(e.x,e.y,e.x+e.h/2,e.y)):d.arc(e.x+e.w/2,e.y+e.h/2,e.h/2,0,2*Math.PI),d.fillStyle="rgba("+r.bgColor.r+","+r.bgColor.g+","+r.bgColor.b+","+e.o+")",d.fill(),d.closePath(),d.beginPath(),d.stroke(),d.fillStyle="rgba("+r.textColor.r+","+r.textColor.g+","+r.textColor.b+","+e.o+")","number"==typeof e.n&&e.n>999?d.fillText((e.n>9999?9:Math.floor(e.n/1e3))+"k+",Math.floor(e.x+e.w/2),Math.floor(e.y+e.h-.2*e.h)):d.fillText(e.n,Math.floor(e.x+e.w/2),Math.floor(e.y+e.h-.15*e.h)),d.closePath()},L.rectangle=function(e){e=I(e);2===e.len?(e.x=e.x-.4*e.w,e.w=1.4*e.w):e.len>=3&&(e.x=e.x-.65*e.w,e.w=1.65*e.w),d.clearRect(0,0,l,s),d.drawImage(u,0,0,l,s),d.beginPath(),d.font=r.fontStyle+" "+Math.floor(e.h*(e.n>99?.9:1))+"px "+r.fontFamily,d.textAlign="center",d.fillStyle="rgba("+r.bgColor.r+","+r.bgColor.g+","+r.bgColor.b+","+e.o+")",d.fillRect(e.x,e.y,e.w,e.h),d.fillStyle="rgba("+r.textColor.r+","+r.textColor.g+","+r.textColor.b+","+e.o+")","number"==typeof e.n&&e.n>999?d.fillText((e.n>9999?9:Math.floor(e.n/1e3))+"k+",Math.floor(e.x+e.w/2),Math.floor(e.y+e.h-.2*e.h)):d.fillText(e.n,Math.floor(e.x+e.w/2),Math.floor(e.y+e.h-.15*e.h)),d.closePath()};var S=function(e,t){t=("string"==typeof t?{animation:t}:t)||{},m=function(){try{if("number"==typeof e?e>0:""!==e){var n={type:"badge",options:{n:e}};if("animation"in t&&N.types[""+t.animation]&&(n.options.animation=""+t.animation),"type"in t&&L[""+t.type]&&(n.options.type=""+t.type),["bgColor","textColor"].forEach(function(e){e in t&&(n.options[e]=i(t[e]))}),["fontStyle","fontFamily"].forEach(function(e){e in t&&(n.options[e]=t[e])}),C.push(n),C.length>100)throw new Error("Too many badges requests in queue.");E.start()}else E.reset()}catch(e){throw new Error("Error setting badge. Message: "+e.message)}},h&&m()},A=function(e){m=function(){try{var t=e.width,i=e.height,n=document.createElement("img"),a=t/l<i/s?t/l:i/s;n.setAttribute("crossOrigin","anonymous"),n.onload=function(){d.clearRect(0,0,l,s),d.drawImage(n,0,0,l,s),k.setIcon(c)},n.setAttribute("src",e.getAttribute("src")),n.height=i/a,n.width=t/a}catch(e){throw new Error("Error setting image. Message: "+e.message)}},h&&m()},D=function(e){m=function(){k.setIconSrc(e)},h&&m()},x=function(e){m=function(){try{if("stop"===e)return g=!0,E.reset(),void(g=!1);e.addEventListener("play",function(){t(this)},!1)}catch(e){throw new Error("Error setting video. Message: "+e.message)}},h&&m()},T=function(e){if(window.URL&&window.URL.createObjectURL||(window.URL=window.URL||{},window.URL.createObjectURL=function(e){return e}),v.supported){var i=!1;navigator.getUserMedia=navigator.getUserMedia||navigator.oGetUserMedia||navigator.msGetUserMedia||navigator.mozGetUserMedia||navigator.webkitGetUserMedia,m=function(){try{if("stop"===e)return g=!0,E.reset(),void(g=!1);i=document.createElement("video"),i.width=l,i.height=s,navigator.getUserMedia({video:!0,audio:!1},function(e){i.src=URL.createObjectURL(e),i.play(),t(i)},function(){})}catch(e){throw new Error("Error setting webcam. Message: "+e.message)}},h&&m()}},B=function(e,t){var n=e;null==t&&"[object Object]"==Object.prototype.toString.call(e)||(n={},n[e]=t);for(var a=Object.keys(n),o=0;o<a.length;o++)"bgColor"==a[o]||"textColor"==a[o]?r[a[o]]=i(n[a[o]]):r[a[o]]=n[a[o]];C.push(p),E.start()},k={};k.getIcons=function(){var e=[];return r.element?e=[r.element]:r.elementId?(e=[w.getElementById(r.elementId)],e[0].setAttribute("href",e[0].getAttribute("src"))):(e=function(){for(var e=[],t=w.getElementsByTagName("head")[0].getElementsByTagName("link"),i=0;i<t.length;i++)/(^|\s)icon(\s|$)/i.test(t[i].getAttribute("rel"))&&e.push(t[i]);return e}(),0===e.length&&(e=[w.createElement("link")],e[0].setAttribute("rel","icon"),w.getElementsByTagName("head")[0].appendChild(e[0]))),e.forEach(function(e){e.setAttribute("type","image/png")}),e},k.setIcon=function(e){var t=e.toDataURL("image/png");k.setIconSrc(t)},k.setIconSrc=function(e){if(r.dataUrl&&r.dataUrl(e),r.element)r.element.setAttribute("href",e),r.element.setAttribute("src",e);else if(r.elementId){var t=w.getElementById(r.elementId);t.setAttribute("href",e),t.setAttribute("src",e)}else if(v.ff||v.opera){var i=o[o.length-1],n=w.createElement("link");o=[n],v.opera&&n.setAttribute("rel","icon"),n.setAttribute("rel","icon"),n.setAttribute("type","image/png"),w.getElementsByTagName("head")[0].appendChild(n),n.setAttribute("href",e),i.parentNode&&i.parentNode.removeChild(i)}else o.forEach(function(t){t.setAttribute("href",e)})};var N={};return N.duration=40,N.types={},N.types.fade=[{x:.4,y:.4,w:.6,h:.6,o:0},{x:.4,y:.4,w:.6,h:.6,o:.1},{x:.4,y:.4,w:.6,h:.6,o:.2},{x:.4,y:.4,w:.6,h:.6,o:.3},{x:.4,y:.4,w:.6,h:.6,o:.4},{x:.4,y:.4,w:.6,h:.6,o:.5},{x:.4,y:.4,w:.6,h:.6,o:.6},{x:.4,y:.4,w:.6,h:.6,o:.7},{x:.4,y:.4,w:.6,h:.6,o:.8},{x:.4,y:.4,w:.6,h:.6,o:.9},{x:.4,y:.4,w:.6,h:.6,o:1}],N.types.none=[{x:.4,y:.4,w:.6,h:.6,o:1}],N.types.pop=[{x:1,y:1,w:0,h:0,o:1},{x:.9,y:.9,w:.1,h:.1,o:1},{x:.8,y:.8,w:.2,h:.2,o:1},{x:.7,y:.7,w:.3,h:.3,o:1},{x:.6,y:.6,w:.4,h:.4,o:1},{x:.5,y:.5,w:.5,h:.5,o:1},{x:.4,y:.4,w:.6,h:.6,o:1}],N.types.popFade=[{x:.75,y:.75,w:0,h:0,o:0},{x:.65,y:.65,w:.1,h:.1,o:.2},{x:.6,y:.6,w:.2,h:.2,o:.4},{x:.55,y:.55,w:.3,h:.3,o:.6},{x:.5,y:.5,w:.4,h:.4,o:.8},{x:.45,y:.45,w:.5,h:.5,o:.9},{x:.4,y:.4,w:.6,h:.6,o:1}],N.types.slide=[{x:.4,y:1,w:.6,h:.6,o:1},{x:.4,y:.9,w:.6,h:.6,o:1},{x:.4,y:.9,w:.6,h:.6,o:1},{x:.4,y:.8,w:.6,h:.6,o:1},{x:.4,y:.7,w:.6,h:.6,o:1},{x:.4,y:.6,w:.6,h:.6,o:1},{x:.4,y:.5,w:.6,h:.6,o:1},{x:.4,y:.4,w:.6,h:.6,o:1}],N.run=function(e,t,i,o){var s=N.types[a()?"none":r.animation];if(o=!0===i?void 0!==o?o:s.length-1:void 0!==o?o:0,t=t||function(){},!(o<s.length&&o>=0))return void t();L[r.type](n(e,s[o])),_=setTimeout(function(){i?o-=1:o+=1,N.run(e,t,i,o)},N.duration),k.setIcon(c)},function(){r=n(y,e),r.bgColor=i(r.bgColor),r.textColor=i(r.textColor),r.position=r.position.toLowerCase(),r.animation=N.types[""+r.animation]?r.animation:y.animation,w=r.win.document;var t=r.position.indexOf("up")>-1,a=r.position.indexOf("left")>-1;if(t||a)for(var h in N.types)for(var p=0;p<N.types[h].length;p++){var f=N.types[h][p];t&&(f.y<.6?f.y=f.y-.4:f.y=f.y-2*f.y+(1-f.w)),a&&(f.x<.6?f.x=f.x-.4:f.x=f.x-2*f.x+(1-f.h)),N.types[h][p]=f}r.type=L[""+r.type]?r.type:y.type,o=k.getIcons(),c=document.createElement("canvas"),u=document.createElement("img");var m=o[o.length-1];m.hasAttribute("href")?(u.setAttribute("crossOrigin","anonymous"),u.onload=function(){s=u.height>0?u.height:32,l=u.width>0?u.width:32,c.height=s,c.width=l,d=c.getContext("2d"),E.ready()},u.setAttribute("src",m.getAttribute("href"))):(s=32,l=32,u.height=s,u.width=l,c.height=s,c.width=l,d=c.getContext("2d"),E.ready())}(),{badge:S,video:x,image:A,rawImageSrc:D,webcam:T,setOpt:B,reset:E.reset,browser:{supported:v.supported}}};void 0!==define&&define.amd?define("favico",[],function(){return e}):"undefined"!=typeof module&&module.exports?module.exports=e:this.Favico=e}(),function(e,t,i){var n=window.matchMedia;"undefined"!=typeof module&&module.exports?module.exports=i(n):"function"==typeof define&&define.amd?define("enquire",[],function(){return t.enquire=i(n)}):t.enquire=i(n)}(0,this,function(e){"use strict";function t(e,t){var i=0,n=e.length;for(i;i<n&&!1!==t(e[i],i);i++);}function i(e){return"[object Array]"===Object.prototype.toString.apply(e)}function n(e){return"function"==typeof e}function a(e){this.options=e,!e.deferSetup&&this.setup()}function r(t,i){this.query=t,this.isUnconditional=i,this.handlers=[],this.mql=e(t);var n=this;this.listener=function(e){n.mql=e,n.assess()},this.mql.addListener(this.listener)}function o(){if(!e)throw new Error("matchMedia not present, legacy browsers require a polyfill");this.queries={},this.browserIsIncapable=!e("only all").matches}return a.prototype={setup:function(){this.options.setup&&this.options.setup(),this.initialised=!0},on:function(){!this.initialised&&this.setup(),this.options.match&&this.options.match()},off:function(){this.options.unmatch&&this.options.unmatch()},destroy:function(){this.options.destroy?this.options.destroy():this.off()},equals:function(e){return this.options===e||this.options.match===e}},r.prototype={addHandler:function(e){var t=new a(e);this.handlers.push(t),this.matches()&&t.on()},removeHandler:function(e){var i=this.handlers;t(i,function(t,n){if(t.equals(e))return t.destroy(),!i.splice(n,1)})},matches:function(){return this.mql.matches||this.isUnconditional},clear:function(){t(this.handlers,function(e){e.destroy()}),this.mql.removeListener(this.listener),this.handlers.length=0},assess:function(){var e=this.matches()?"on":"off";t(this.handlers,function(t){t[e]()})}},o.prototype={register:function(e,a,o){var s=this.queries,l=o&&this.browserIsIncapable;return s[e]||(s[e]=new r(e,l)),n(a)&&(a={match:a}),i(a)||(a=[a]),t(a,function(t){n(t)&&(t={match:t}),s[e].addHandler(t)}),this},unregister:function(e,t){var i=this.queries[e];return i&&(t?i.removeHandler(t):(i.clear(),delete this.queries[e])),this}},new o}),function e(t,i,n){
+function a(o,s){if(!i[o]){if(!t[o]){var l="function"==typeof require&&require;if(!s&&l)return l(o,!0);if(r)return r(o,!0);var c=new Error("Cannot find module '"+o+"'");throw c.code="MODULE_NOT_FOUND",c}var d=i[o]={exports:{}};t[o][0].call(d.exports,function(e){var i=t[o][1][e];return a(i||e)},d,d.exports,e,t,i,n)}return i[o].exports}for(var r="function"==typeof require&&require,o=0;o<n.length;o++)a(n[o]);return a}({1:[function(e,t,i){"use strict";var n=e("../main");"function"==typeof define&&define.amd?define("perfect-scrollbar",n):(window.PerfectScrollbar=n,void 0===window.Ps&&(window.Ps=n))},{"../main":7}],2:[function(e,t,i){"use strict";function n(e,t){var i=e.className.split(" ");i.indexOf(t)<0&&i.push(t),e.className=i.join(" ")}function a(e,t){var i=e.className.split(" "),n=i.indexOf(t);n>=0&&i.splice(n,1),e.className=i.join(" ")}i.add=function(e,t){e.classList?e.classList.add(t):n(e,t)},i.remove=function(e,t){e.classList?e.classList.remove(t):a(e,t)},i.list=function(e){return e.classList?Array.prototype.slice.apply(e.classList):e.className.split(" ")}},{}],3:[function(e,t,i){"use strict";function n(e,t){return window.getComputedStyle(e)[t]}function a(e,t,i){return"number"==typeof i&&(i=i.toString()+"px"),e.style[t]=i,e}function r(e,t){for(var i in t){var n=t[i];"number"==typeof n&&(n=n.toString()+"px"),e.style[i]=n}return e}var o={};o.e=function(e,t){var i=document.createElement(e);return i.className=t,i},o.appendTo=function(e,t){return t.appendChild(e),e},o.css=function(e,t,i){return"object"==typeof t?r(e,t):void 0===i?n(e,t):a(e,t,i)},o.matches=function(e,t){return void 0!==e.matches?e.matches(t):void 0!==e.matchesSelector?e.matchesSelector(t):void 0!==e.webkitMatchesSelector?e.webkitMatchesSelector(t):void 0!==e.mozMatchesSelector?e.mozMatchesSelector(t):void 0!==e.msMatchesSelector?e.msMatchesSelector(t):void 0},o.remove=function(e){void 0!==e.remove?e.remove():e.parentNode&&e.parentNode.removeChild(e)},o.queryChildren=function(e,t){return Array.prototype.filter.call(e.childNodes,function(e){return o.matches(e,t)})},t.exports=o},{}],4:[function(e,t,i){"use strict";var n=function(e){this.element=e,this.events={}};n.prototype.bind=function(e,t){void 0===this.events[e]&&(this.events[e]=[]),this.events[e].push(t),this.element.addEventListener(e,t,!1)},n.prototype.unbind=function(e,t){var i=void 0!==t;this.events[e]=this.events[e].filter(function(n){return!(!i||n===t)||(this.element.removeEventListener(e,n,!1),!1)},this)},n.prototype.unbindAll=function(){for(var e in this.events)this.unbind(e)};var a=function(){this.eventElements=[]};a.prototype.eventElement=function(e){var t=this.eventElements.filter(function(t){return t.element===e})[0];return void 0===t&&(t=new n(e),this.eventElements.push(t)),t},a.prototype.bind=function(e,t,i){this.eventElement(e).bind(t,i)},a.prototype.unbind=function(e,t,i){this.eventElement(e).unbind(t,i)},a.prototype.unbindAll=function(){for(var e=0;e<this.eventElements.length;e++)this.eventElements[e].unbindAll()},a.prototype.once=function(e,t,i){var n=this.eventElement(e),a=function(e){n.unbind(t,a),i(e)};n.bind(t,a)},t.exports=a},{}],5:[function(e,t,i){"use strict";t.exports=function(){function e(){return Math.floor(65536*(1+Math.random())).toString(16).substring(1)}return function(){return e()+e()+"-"+e()+"-"+e()+"-"+e()+"-"+e()+e()+e()}}()},{}],6:[function(e,t,i){"use strict";var n=e("./class"),a=e("./dom"),r=i.toInt=function(e){return parseInt(e,10)||0},o=i.clone=function(e){if(e){if(e.constructor===Array)return e.map(o);if("object"==typeof e){var t={};for(var i in e)t[i]=o(e[i]);return t}return e}return null};i.extend=function(e,t){var i=o(e);for(var n in t)i[n]=o(t[n]);return i},i.isEditable=function(e){return a.matches(e,"input,[contenteditable]")||a.matches(e,"select,[contenteditable]")||a.matches(e,"textarea,[contenteditable]")||a.matches(e,"button,[contenteditable]")},i.removePsClasses=function(e){for(var t=n.list(e),i=0;i<t.length;i++){var a=t[i];0===a.indexOf("ps-")&&n.remove(e,a)}},i.outerWidth=function(e){return r(a.css(e,"width"))+r(a.css(e,"paddingLeft"))+r(a.css(e,"paddingRight"))+r(a.css(e,"borderLeftWidth"))+r(a.css(e,"borderRightWidth"))},i.startScrolling=function(e,t){n.add(e,"ps-in-scrolling"),void 0!==t?n.add(e,"ps-"+t):(n.add(e,"ps-x"),n.add(e,"ps-y"))},i.stopScrolling=function(e,t){n.remove(e,"ps-in-scrolling"),void 0!==t?n.remove(e,"ps-"+t):(n.remove(e,"ps-x"),n.remove(e,"ps-y"))},i.env={isWebKit:"WebkitAppearance"in document.documentElement.style,supportsTouch:"ontouchstart"in window||window.DocumentTouch&&document instanceof window.DocumentTouch,supportsIePointer:null!==window.navigator.msMaxTouchPoints}},{"./class":2,"./dom":3}],7:[function(e,t,i){"use strict";var n=e("./plugin/destroy"),a=e("./plugin/initialize"),r=e("./plugin/update");t.exports={initialize:a,update:r,destroy:n}},{"./plugin/destroy":9,"./plugin/initialize":17,"./plugin/update":21}],8:[function(e,t,i){"use strict";t.exports={handlers:["click-rail","drag-scrollbar","keyboard","wheel","touch"],maxScrollbarLength:null,minScrollbarLength:null,scrollXMarginOffset:0,scrollYMarginOffset:0,suppressScrollX:!1,suppressScrollY:!1,swipePropagation:!0,useBothWheelAxes:!1,wheelPropagation:!1,wheelSpeed:1,theme:"default"}},{}],9:[function(e,t,i){"use strict";var n=e("../lib/helper"),a=e("../lib/dom"),r=e("./instances");t.exports=function(e){var t=r.get(e);t&&(t.event.unbindAll(),a.remove(t.scrollbarX),a.remove(t.scrollbarY),a.remove(t.scrollbarXRail),a.remove(t.scrollbarYRail),n.removePsClasses(e),r.remove(e))}},{"../lib/dom":3,"../lib/helper":6,"./instances":18}],10:[function(e,t,i){"use strict";function n(e,t){function i(e){return e.getBoundingClientRect()}var n=function(e){e.stopPropagation()};t.event.bind(t.scrollbarY,"click",n),t.event.bind(t.scrollbarYRail,"click",function(n){var a=n.pageY-window.pageYOffset-i(t.scrollbarYRail).top,s=a>t.scrollbarYTop?1:-1;o(e,"top",e.scrollTop+s*t.containerHeight),r(e),n.stopPropagation()}),t.event.bind(t.scrollbarX,"click",n),t.event.bind(t.scrollbarXRail,"click",function(n){var a=n.pageX-window.pageXOffset-i(t.scrollbarXRail).left,s=a>t.scrollbarXLeft?1:-1;o(e,"left",e.scrollLeft+s*t.containerWidth),r(e),n.stopPropagation()})}var a=e("../instances"),r=e("../update-geometry"),o=e("../update-scroll");t.exports=function(e){n(e,a.get(e))}},{"../instances":18,"../update-geometry":19,"../update-scroll":20}],11:[function(e,t,i){"use strict";function n(e,t){function i(i){var a=n+i*t.railXRatio,o=Math.max(0,t.scrollbarXRail.getBoundingClientRect().left)+t.railXRatio*(t.railXWidth-t.scrollbarXWidth);t.scrollbarXLeft=a<0?0:a>o?o:a;var s=r.toInt(t.scrollbarXLeft*(t.contentWidth-t.containerWidth)/(t.containerWidth-t.railXRatio*t.scrollbarXWidth))-t.negativeScrollAdjustment;c(e,"left",s)}var n=null,a=null,s=function(t){i(t.pageX-a),l(e),t.stopPropagation(),t.preventDefault()},d=function(){r.stopScrolling(e,"x"),t.event.unbind(t.ownerDocument,"mousemove",s)};t.event.bind(t.scrollbarX,"mousedown",function(i){a=i.pageX,n=r.toInt(o.css(t.scrollbarX,"left"))*t.railXRatio,r.startScrolling(e,"x"),t.event.bind(t.ownerDocument,"mousemove",s),t.event.once(t.ownerDocument,"mouseup",d),i.stopPropagation(),i.preventDefault()})}function a(e,t){function i(i){var a=n+i*t.railYRatio,o=Math.max(0,t.scrollbarYRail.getBoundingClientRect().top)+t.railYRatio*(t.railYHeight-t.scrollbarYHeight);t.scrollbarYTop=a<0?0:a>o?o:a;var s=r.toInt(t.scrollbarYTop*(t.contentHeight-t.containerHeight)/(t.containerHeight-t.railYRatio*t.scrollbarYHeight));c(e,"top",s)}var n=null,a=null,s=function(t){i(t.pageY-a),l(e),t.stopPropagation(),t.preventDefault()},d=function(){r.stopScrolling(e,"y"),t.event.unbind(t.ownerDocument,"mousemove",s)};t.event.bind(t.scrollbarY,"mousedown",function(i){a=i.pageY,n=r.toInt(o.css(t.scrollbarY,"top"))*t.railYRatio,r.startScrolling(e,"y"),t.event.bind(t.ownerDocument,"mousemove",s),t.event.once(t.ownerDocument,"mouseup",d),i.stopPropagation(),i.preventDefault()})}var r=e("../../lib/helper"),o=e("../../lib/dom"),s=e("../instances"),l=e("../update-geometry"),c=e("../update-scroll");t.exports=function(e){var t=s.get(e);n(e,t),a(e,t)}},{"../../lib/dom":3,"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],12:[function(e,t,i){"use strict";function n(e,t){function i(i,n){var a=e.scrollTop;if(0===i){if(!t.scrollbarYActive)return!1;if(0===a&&n>0||a>=t.contentHeight-t.containerHeight&&n<0)return!t.settings.wheelPropagation}var r=e.scrollLeft;if(0===n){if(!t.scrollbarXActive)return!1;if(0===r&&i<0||r>=t.contentWidth-t.containerWidth&&i>0)return!t.settings.wheelPropagation}return!0}var n=!1;t.event.bind(e,"mouseenter",function(){n=!0}),t.event.bind(e,"mouseleave",function(){n=!1});var o=!1;t.event.bind(t.ownerDocument,"keydown",function(c){if(!(c.isDefaultPrevented&&c.isDefaultPrevented()||c.defaultPrevented)){var d=r.matches(t.scrollbarX,":focus")||r.matches(t.scrollbarY,":focus");if(n||d){var u=document.activeElement?document.activeElement:t.ownerDocument.activeElement;if(u){if("IFRAME"===u.tagName)u=u.contentDocument.activeElement;else for(;u.shadowRoot;)u=u.shadowRoot.activeElement;if(a.isEditable(u))return}var h=0,p=0;switch(c.which){case 37:h=c.metaKey?-t.contentWidth:c.altKey?-t.containerWidth:-30;break;case 38:p=c.metaKey?t.contentHeight:c.altKey?t.containerHeight:30;break;case 39:h=c.metaKey?t.contentWidth:c.altKey?t.containerWidth:30;break;case 40:p=c.metaKey?-t.contentHeight:c.altKey?-t.containerHeight:-30;break;case 33:p=90;break;case 32:p=c.shiftKey?90:-90;break;case 34:p=-90;break;case 35:p=c.ctrlKey?-t.contentHeight:-t.containerHeight;break;case 36:p=c.ctrlKey?e.scrollTop:t.containerHeight;break;default:return}l(e,"top",e.scrollTop-p),l(e,"left",e.scrollLeft+h),s(e),o=i(h,p),o&&c.preventDefault()}}})}var a=e("../../lib/helper"),r=e("../../lib/dom"),o=e("../instances"),s=e("../update-geometry"),l=e("../update-scroll");t.exports=function(e){n(e,o.get(e))}},{"../../lib/dom":3,"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],13:[function(e,t,i){"use strict";function n(e,t){function i(i,n){var a=e.scrollTop;if(0===i){if(!t.scrollbarYActive)return!1;if(0===a&&n>0||a>=t.contentHeight-t.containerHeight&&n<0)return!t.settings.wheelPropagation}var r=e.scrollLeft;if(0===n){if(!t.scrollbarXActive)return!1;if(0===r&&i<0||r>=t.contentWidth-t.containerWidth&&i>0)return!t.settings.wheelPropagation}return!0}function n(e){var t=e.deltaX,i=-1*e.deltaY;return void 0!==t&&void 0!==i||(t=-1*e.wheelDeltaX/6,i=e.wheelDeltaY/6),e.deltaMode&&1===e.deltaMode&&(t*=10,i*=10),t!==t&&i!==i&&(t=0,i=e.wheelDelta),e.shiftKey?[-i,-t]:[t,i]}function a(t,i){var n=e.querySelector("textarea:hover, select[multiple]:hover, .ps-child:hover");if(n){if(!window.getComputedStyle(n).overflow.match(/(scroll|auto)/))return!1;var a=n.scrollHeight-n.clientHeight;if(a>0&&!(0===n.scrollTop&&i>0||n.scrollTop===a&&i<0))return!0;var r=n.scrollLeft-n.clientWidth;if(r>0&&!(0===n.scrollLeft&&t<0||n.scrollLeft===r&&t>0))return!0}return!1}function s(s){var c=n(s),d=c[0],u=c[1];a(d,u)||(l=!1,t.settings.useBothWheelAxes?t.scrollbarYActive&&!t.scrollbarXActive?(u?o(e,"top",e.scrollTop-u*t.settings.wheelSpeed):o(e,"top",e.scrollTop+d*t.settings.wheelSpeed),l=!0):t.scrollbarXActive&&!t.scrollbarYActive&&(d?o(e,"left",e.scrollLeft+d*t.settings.wheelSpeed):o(e,"left",e.scrollLeft-u*t.settings.wheelSpeed),l=!0):(o(e,"top",e.scrollTop-u*t.settings.wheelSpeed),o(e,"left",e.scrollLeft+d*t.settings.wheelSpeed)),r(e),(l=l||i(d,u))&&(s.stopPropagation(),s.preventDefault()))}var l=!1;void 0!==window.onwheel?t.event.bind(e,"wheel",s):void 0!==window.onmousewheel&&t.event.bind(e,"mousewheel",s)}var a=e("../instances"),r=e("../update-geometry"),o=e("../update-scroll");t.exports=function(e){n(e,a.get(e))}},{"../instances":18,"../update-geometry":19,"../update-scroll":20}],14:[function(e,t,i){"use strict";function n(e,t){t.event.bind(e,"scroll",function(){r(e)})}var a=e("../instances"),r=e("../update-geometry");t.exports=function(e){n(e,a.get(e))}},{"../instances":18,"../update-geometry":19}],15:[function(e,t,i){"use strict";function n(e,t){function i(){var e=window.getSelection?window.getSelection():document.getSelection?document.getSelection():"";return 0===e.toString().length?null:e.getRangeAt(0).commonAncestorContainer}function n(){c||(c=setInterval(function(){if(!r.get(e))return void clearInterval(c);s(e,"top",e.scrollTop+d.top),s(e,"left",e.scrollLeft+d.left),o(e)},50))}function l(){c&&(clearInterval(c),c=null),a.stopScrolling(e)}var c=null,d={top:0,left:0},u=!1;t.event.bind(t.ownerDocument,"selectionchange",function(){e.contains(i())?u=!0:(u=!1,l())}),t.event.bind(window,"mouseup",function(){u&&(u=!1,l())}),t.event.bind(window,"keyup",function(){u&&(u=!1,l())}),t.event.bind(window,"mousemove",function(t){if(u){var i={x:t.pageX,y:t.pageY},r={left:e.offsetLeft,right:e.offsetLeft+e.offsetWidth,top:e.offsetTop,bottom:e.offsetTop+e.offsetHeight};i.x<r.left+3?(d.left=-5,a.startScrolling(e,"x")):i.x>r.right-3?(d.left=5,a.startScrolling(e,"x")):d.left=0,i.y<r.top+3?(d.top=r.top+3-i.y<5?-5:-20,a.startScrolling(e,"y")):i.y>r.bottom-3?(d.top=i.y-r.bottom+3<5?5:20,a.startScrolling(e,"y")):d.top=0,0===d.top&&0===d.left?l():n()}})}var a=e("../../lib/helper"),r=e("../instances"),o=e("../update-geometry"),s=e("../update-scroll");t.exports=function(e){n(e,r.get(e))}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],16:[function(e,t,i){"use strict";function n(e,t,i,n){function a(i,n){var a=e.scrollTop,r=e.scrollLeft,o=Math.abs(i),s=Math.abs(n);if(s>o){if(n<0&&a===t.contentHeight-t.containerHeight||n>0&&0===a)return!t.settings.swipePropagation}else if(o>s&&(i<0&&r===t.contentWidth-t.containerWidth||i>0&&0===r))return!t.settings.swipePropagation;return!0}function l(t,i){s(e,"top",e.scrollTop-i),s(e,"left",e.scrollLeft-t),o(e)}function c(){w=!0}function d(){w=!1}function u(e){return e.targetTouches?e.targetTouches[0]:e}function h(e){return!(!e.targetTouches||1!==e.targetTouches.length)||!(!e.pointerType||"mouse"===e.pointerType||e.pointerType===e.MSPOINTER_TYPE_MOUSE)}function p(e){if(h(e)){y=!0;var t=u(e);g.pageX=t.pageX,g.pageY=t.pageY,v=(new Date).getTime(),null!==b&&clearInterval(b),e.stopPropagation()}}function f(e){if(!y&&t.settings.swipePropagation&&p(e),!w&&y&&h(e)){var i=u(e),n={pageX:i.pageX,pageY:i.pageY},r=n.pageX-g.pageX,o=n.pageY-g.pageY;l(r,o),g=n;var s=(new Date).getTime(),c=s-v;c>0&&(_.x=r/c,_.y=o/c,v=s),a(r,o)&&(e.stopPropagation(),e.preventDefault())}}function m(){!w&&y&&(y=!1,clearInterval(b),b=setInterval(function(){return r.get(e)&&(_.x||_.y)?Math.abs(_.x)<.01&&Math.abs(_.y)<.01?void clearInterval(b):(l(30*_.x,30*_.y),_.x*=.8,void(_.y*=.8)):void clearInterval(b)},10))}var g={},v=0,_={},b=null,w=!1,y=!1;i?(t.event.bind(window,"touchstart",c),t.event.bind(window,"touchend",d),t.event.bind(e,"touchstart",p),t.event.bind(e,"touchmove",f),t.event.bind(e,"touchend",m)):n&&(window.PointerEvent?(t.event.bind(window,"pointerdown",c),t.event.bind(window,"pointerup",d),t.event.bind(e,"pointerdown",p),t.event.bind(e,"pointermove",f),t.event.bind(e,"pointerup",m)):window.MSPointerEvent&&(t.event.bind(window,"MSPointerDown",c),t.event.bind(window,"MSPointerUp",d),t.event.bind(e,"MSPointerDown",p),t.event.bind(e,"MSPointerMove",f),t.event.bind(e,"MSPointerUp",m)))}var a=e("../../lib/helper"),r=e("../instances"),o=e("../update-geometry"),s=e("../update-scroll");t.exports=function(e){if(a.env.supportsTouch||a.env.supportsIePointer){n(e,r.get(e),a.env.supportsTouch,a.env.supportsIePointer)}}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],17:[function(e,t,i){"use strict";var n=e("../lib/helper"),a=e("../lib/class"),r=e("./instances"),o=e("./update-geometry"),s={"click-rail":e("./handler/click-rail"),"drag-scrollbar":e("./handler/drag-scrollbar"),keyboard:e("./handler/keyboard"),wheel:e("./handler/mouse-wheel"),touch:e("./handler/touch"),selection:e("./handler/selection")},l=e("./handler/native-scroll");t.exports=function(e,t){t="object"==typeof t?t:{},a.add(e,"ps-container");var i=r.add(e);i.settings=n.extend(i.settings,t),a.add(e,"ps-theme-"+i.settings.theme),i.settings.handlers.forEach(function(t){s[t](e)}),l(e),o(e)}},{"../lib/class":2,"../lib/helper":6,"./handler/click-rail":10,"./handler/drag-scrollbar":11,"./handler/keyboard":12,"./handler/mouse-wheel":13,"./handler/native-scroll":14,"./handler/selection":15,"./handler/touch":16,"./instances":18,"./update-geometry":19}],18:[function(e,t,i){"use strict";function n(e){function t(){l.add(e,"ps-focus")}function i(){l.remove(e,"ps-focus")}var n=this;n.settings=s.clone(c),n.containerWidth=null,n.containerHeight=null,n.contentWidth=null,n.contentHeight=null,n.isRtl="rtl"===d.css(e,"direction"),n.isNegativeScroll=function(){var t=e.scrollLeft,i=null;return e.scrollLeft=-1,i=e.scrollLeft<0,e.scrollLeft=t,i}(),n.negativeScrollAdjustment=n.isNegativeScroll?e.scrollWidth-e.clientWidth:0,n.event=new u,n.ownerDocument=e.ownerDocument||document,n.scrollbarXRail=d.appendTo(d.e("div","ps-scrollbar-x-rail"),e),n.scrollbarX=d.appendTo(d.e("div","ps-scrollbar-x"),n.scrollbarXRail),n.scrollbarX.setAttribute("tabindex",0),n.event.bind(n.scrollbarX,"focus",t),n.event.bind(n.scrollbarX,"blur",i),n.scrollbarXActive=null,n.scrollbarXWidth=null,n.scrollbarXLeft=null,n.scrollbarXBottom=s.toInt(d.css(n.scrollbarXRail,"bottom")),n.isScrollbarXUsingBottom=n.scrollbarXBottom===n.scrollbarXBottom,n.scrollbarXTop=n.isScrollbarXUsingBottom?null:s.toInt(d.css(n.scrollbarXRail,"top")),n.railBorderXWidth=s.toInt(d.css(n.scrollbarXRail,"borderLeftWidth"))+s.toInt(d.css(n.scrollbarXRail,"borderRightWidth")),d.css(n.scrollbarXRail,"display","block"),n.railXMarginWidth=s.toInt(d.css(n.scrollbarXRail,"marginLeft"))+s.toInt(d.css(n.scrollbarXRail,"marginRight")),d.css(n.scrollbarXRail,"display",""),n.railXWidth=null,n.railXRatio=null,n.scrollbarYRail=d.appendTo(d.e("div","ps-scrollbar-y-rail"),e),n.scrollbarY=d.appendTo(d.e("div","ps-scrollbar-y"),n.scrollbarYRail),n.scrollbarY.setAttribute("tabindex",0),n.event.bind(n.scrollbarY,"focus",t),n.event.bind(n.scrollbarY,"blur",i),n.scrollbarYActive=null,n.scrollbarYHeight=null,n.scrollbarYTop=null,n.scrollbarYRight=s.toInt(d.css(n.scrollbarYRail,"right")),n.isScrollbarYUsingRight=n.scrollbarYRight===n.scrollbarYRight,n.scrollbarYLeft=n.isScrollbarYUsingRight?null:s.toInt(d.css(n.scrollbarYRail,"left")),n.scrollbarYOuterWidth=n.isRtl?s.outerWidth(n.scrollbarY):null,n.railBorderYWidth=s.toInt(d.css(n.scrollbarYRail,"borderTopWidth"))+s.toInt(d.css(n.scrollbarYRail,"borderBottomWidth")),d.css(n.scrollbarYRail,"display","block"),n.railYMarginHeight=s.toInt(d.css(n.scrollbarYRail,"marginTop"))+s.toInt(d.css(n.scrollbarYRail,"marginBottom")),d.css(n.scrollbarYRail,"display",""),n.railYHeight=null,n.railYRatio=null}function a(e){return e.getAttribute("data-ps-id")}function r(e,t){e.setAttribute("data-ps-id",t)}function o(e){e.removeAttribute("data-ps-id")}var s=e("../lib/helper"),l=e("../lib/class"),c=e("./default-setting"),d=e("../lib/dom"),u=e("../lib/event-manager"),h=e("../lib/guid"),p={};i.add=function(e){var t=h();return r(e,t),p[t]=new n(e),p[t]},i.remove=function(e){delete p[a(e)],o(e)},i.get=function(e){return p[a(e)]}},{"../lib/class":2,"../lib/dom":3,"../lib/event-manager":4,"../lib/guid":5,"../lib/helper":6,"./default-setting":8}],19:[function(e,t,i){"use strict";function n(e,t){return e.settings.minScrollbarLength&&(t=Math.max(t,e.settings.minScrollbarLength)),e.settings.maxScrollbarLength&&(t=Math.min(t,e.settings.maxScrollbarLength)),t}function a(e,t){var i={width:t.railXWidth};t.isRtl?i.left=t.negativeScrollAdjustment+e.scrollLeft+t.containerWidth-t.contentWidth:i.left=e.scrollLeft,t.isScrollbarXUsingBottom?i.bottom=t.scrollbarXBottom-e.scrollTop:i.top=t.scrollbarXTop+e.scrollTop,s.css(t.scrollbarXRail,i);var n={top:e.scrollTop,height:t.railYHeight};t.isScrollbarYUsingRight?t.isRtl?n.right=t.contentWidth-(t.negativeScrollAdjustment+e.scrollLeft)-t.scrollbarYRight-t.scrollbarYOuterWidth:n.right=t.scrollbarYRight-e.scrollLeft:t.isRtl?n.left=t.negativeScrollAdjustment+e.scrollLeft+2*t.containerWidth-t.contentWidth-t.scrollbarYLeft-t.scrollbarYOuterWidth:n.left=t.scrollbarYLeft+e.scrollLeft,s.css(t.scrollbarYRail,n),s.css(t.scrollbarX,{left:t.scrollbarXLeft,width:t.scrollbarXWidth-t.railBorderXWidth}),s.css(t.scrollbarY,{top:t.scrollbarYTop,height:t.scrollbarYHeight-t.railBorderYWidth})}var r=e("../lib/helper"),o=e("../lib/class"),s=e("../lib/dom"),l=e("./instances"),c=e("./update-scroll");t.exports=function(e){var t=l.get(e);t.containerWidth=e.clientWidth,t.containerHeight=e.clientHeight,t.contentWidth=e.scrollWidth,t.contentHeight=e.scrollHeight;var i;e.contains(t.scrollbarXRail)||(i=s.queryChildren(e,".ps-scrollbar-x-rail"),i.length>0&&i.forEach(function(e){s.remove(e)}),s.appendTo(t.scrollbarXRail,e)),e.contains(t.scrollbarYRail)||(i=s.queryChildren(e,".ps-scrollbar-y-rail"),i.length>0&&i.forEach(function(e){s.remove(e)}),s.appendTo(t.scrollbarYRail,e)),!t.settings.suppressScrollX&&t.containerWidth+t.settings.scrollXMarginOffset<t.contentWidth?(t.scrollbarXActive=!0,t.railXWidth=t.containerWidth-t.railXMarginWidth,t.railXRatio=t.containerWidth/t.railXWidth,t.scrollbarXWidth=n(t,r.toInt(t.railXWidth*t.containerWidth/t.contentWidth)),t.scrollbarXLeft=r.toInt((t.negativeScrollAdjustment+e.scrollLeft)*(t.railXWidth-t.scrollbarXWidth)/(t.contentWidth-t.containerWidth))):t.scrollbarXActive=!1,!t.settings.suppressScrollY&&t.containerHeight+t.settings.scrollYMarginOffset<t.contentHeight?(t.scrollbarYActive=!0,t.railYHeight=t.containerHeight-t.railYMarginHeight,t.railYRatio=t.containerHeight/t.railYHeight,t.scrollbarYHeight=n(t,r.toInt(t.railYHeight*t.containerHeight/t.contentHeight)),t.scrollbarYTop=r.toInt(e.scrollTop*(t.railYHeight-t.scrollbarYHeight)/(t.contentHeight-t.containerHeight))):t.scrollbarYActive=!1,t.scrollbarXLeft>=t.railXWidth-t.scrollbarXWidth&&(t.scrollbarXLeft=t.railXWidth-t.scrollbarXWidth),t.scrollbarYTop>=t.railYHeight-t.scrollbarYHeight&&(t.scrollbarYTop=t.railYHeight-t.scrollbarYHeight),a(e,t),t.scrollbarXActive?o.add(e,"ps-active-x"):(o.remove(e,"ps-active-x"),t.scrollbarXWidth=0,t.scrollbarXLeft=0,c(e,"left",0)),t.scrollbarYActive?o.add(e,"ps-active-y"):(o.remove(e,"ps-active-y"),t.scrollbarYHeight=0,t.scrollbarYTop=0,c(e,"top",0))}},{"../lib/class":2,"../lib/dom":3,"../lib/helper":6,"./instances":18,"./update-scroll":20}],20:[function(e,t,i){"use strict";var n,a,r=e("./instances"),o=function(e){var t=document.createEvent("Event");return t.initEvent(e,!0,!0),t};t.exports=function(e,t,i){if(void 0===e)throw"You must provide an element to the update-scroll function";if(void 0===t)throw"You must provide an axis to the update-scroll function";if(void 0===i)throw"You must provide a value to the update-scroll function";"top"===t&&i<=0&&(e.scrollTop=i=0,e.dispatchEvent(o("ps-y-reach-start"))),"left"===t&&i<=0&&(e.scrollLeft=i=0,e.dispatchEvent(o("ps-x-reach-start")));var s=r.get(e);"top"===t&&i>=s.contentHeight-s.containerHeight&&(i=s.contentHeight-s.containerHeight,i-e.scrollTop<=1?i=e.scrollTop:e.scrollTop=i,e.dispatchEvent(o("ps-y-reach-end"))),"left"===t&&i>=s.contentWidth-s.containerWidth&&(i=s.contentWidth-s.containerWidth,i-e.scrollLeft<=1?i=e.scrollLeft:e.scrollLeft=i,e.dispatchEvent(o("ps-x-reach-end"))),n||(n=e.scrollTop),a||(a=e.scrollLeft),"top"===t&&i<n&&e.dispatchEvent(o("ps-scroll-up")),"top"===t&&i>n&&e.dispatchEvent(o("ps-scroll-down")),"left"===t&&i<a&&e.dispatchEvent(o("ps-scroll-left")),"left"===t&&i>a&&e.dispatchEvent(o("ps-scroll-right")),"top"===t&&(e.scrollTop=n=i,e.dispatchEvent(o("ps-scroll-y"))),"left"===t&&(e.scrollLeft=a=i,e.dispatchEvent(o("ps-scroll-x")))}},{"./instances":18}],21:[function(e,t,i){"use strict";var n=e("../lib/helper"),a=e("../lib/dom"),r=e("./instances"),o=e("./update-geometry"),s=e("./update-scroll");t.exports=function(e){var t=r.get(e);t&&(t.negativeScrollAdjustment=t.isNegativeScroll?e.scrollWidth-e.clientWidth:0,a.css(t.scrollbarXRail,"display","block"),a.css(t.scrollbarYRail,"display","block"),t.railXMarginWidth=n.toInt(a.css(t.scrollbarXRail,"marginLeft"))+n.toInt(a.css(t.scrollbarXRail,"marginRight")),t.railYMarginHeight=n.toInt(a.css(t.scrollbarYRail,"marginTop"))+n.toInt(a.css(t.scrollbarYRail,"marginBottom")),a.css(t.scrollbarXRail,"display","none"),a.css(t.scrollbarYRail,"display","none"),o(e),s(e,"top",e.scrollTop),s(e,"left",e.scrollLeft),a.css(t.scrollbarXRail,"display",""),a.css(t.scrollbarYRail,"display",""))}},{"../lib/dom":3,"../lib/helper":6,"./instances":18,"./update-geometry":19,"./update-scroll":20}]},{},[1]),define("WoltLabSuite/Core/Date/Util",["Language"],function(e){"use strict";return{formatDate:function(t){return this.format(t,e.get("wcf.date.dateFormat"))},formatTime:function(t){return this.format(t,e.get("wcf.date.timeFormat"))},formatDateTime:function(t){return this.format(t,e.get("wcf.date.dateTimeFormat").replace(/%date%/,e.get("wcf.date.dateFormat")).replace(/%time%/,e.get("wcf.date.timeFormat")))},format:function(t,i){var n,a="";"c"===i&&(i="Y-m-dTH:i:sP");for(var r=0,o=i.length;r<o;r++){switch(i[r]){case"s":n=("0"+t.getSeconds().toString()).slice(-2);break;case"i":n=t.getMinutes(),n<10&&(n="0"+n);break;case"a":n=t.getHours()>11?"pm":"am";break;case"g":n=t.getHours(),0===n?n=12:n>12&&(n-=12);break;case"h":n=t.getHours(),0===n?n=12:n>12&&(n-=12),n=("0"+n.toString()).slice(-2);break;case"A":n=t.getHours()>11?"PM":"AM";break;case"G":n=t.getHours();break;case"H":n=t.getHours(),n=("0"+n.toString()).slice(-2);break;case"d":n=t.getDate(),n=("0"+n.toString()).slice(-2);break;case"j":n=t.getDate();break;case"l":n=e.get("__days")[t.getDay()];break;case"D":n=e.get("__daysShort")[t.getDay()];break;case"S":n="";break;case"m":n=t.getMonth()+1,n=("0"+n.toString()).slice(-2);break;case"n":n=t.getMonth()+1;break;case"F":n=e.get("__months")[t.getMonth()];break;case"M":n=e.get("__monthsShort")[t.getMonth()];break;case"y":n=t.getYear().toString().replace(/^\d{2}/,"");break;case"Y":n=t.getFullYear();break;case"P":var s=t.getTimezoneOffset();n=s>0?"-":"+",s=Math.abs(s),n+=("0"+(~~(s/60)).toString()).slice(-2),n+=":",n+=("0"+(s%60).toString()).slice(-2);break;case"r":n=t.toString();break;case"U":n=Math.round(t.getTime()/1e3);break;case"\\":n="",r+1<o&&(n=i[++r]);break;default:n=i[r]}a+=n}return a},gmdate:function(e){return e instanceof Date||(e=new Date),Math.round(Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDay(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds())/1e3)},getTimeElement:function(t){var i=elCreate("time");i.className="datetime";var n=this.formatDate(t),a=this.formatTime(t);return elAttr(i,"datetime",this.format(t,"c")),elData(i,"timestamp",(t.getTime()-t.getMilliseconds())/1e3),elData(i,"date",n),elData(i,"time",a),elData(i,"offset",60*t.getTimezoneOffset()),t.getTime()>Date.now()&&(elData(i,"is-future-date","true"),i.textContent=e.get("wcf.date.dateTimeFormat").replace("%time%",a).replace("%date%",n)),i},getTimezoneDate:function(e,t){var i=new Date(e),n=6e4*i.getTimezoneOffset();return new Date(e+n+t)}}}),define("WoltLabSuite/Core/Timer/Repeating",[],function(){"use strict";function e(e,t){if("function"!=typeof e)throw new TypeError("Expected a valid callback as first argument.");if(t<0||t>864e5)throw new RangeError("Invalid delta "+t+". Delta must be in the interval [0, 86400000].");this._callback=e.bind(void 0,this),this._delta=t,this._timer=void 0,this.restart()}return e.prototype={restart:function(){this.stop(),this._timer=setInterval(this._callback,this._delta)},stop:function(){void 0!==this._timer&&(clearInterval(this._timer),this._timer=void 0)},setDelta:function(e){this._delta=e,this.restart()}},e}),define("WoltLabSuite/Core/Date/Time/Relative",["Dom/ChangeListener","Language","WoltLabSuite/Core/Date/Util","WoltLabSuite/Core/Timer/Repeating"],function(e,t,i,n){"use strict";var a=elByTag("time"),r=!0,o=!1,s=null;return{setup:function(){new n(this._refresh.bind(this),6e4),e.add("WoltLabSuite/Core/Date/Time/Relative",this._refresh.bind(this)),document.addEventListener("visibilitychange",this._onVisibilityChange.bind(this))},_onVisibilityChange:function(){document.hidden?(r=!1,o=!1):(r=!0,o&&(this._refresh(),o=!1))},_refresh:function(){if(!r)return void(o||(o=!0));var e=new Date,n=(e.getTime()-e.getMilliseconds())/1e3;null===s&&(s=n-window.TIME_NOW);for(var l=0,c=a.length;l<c;l++){var d=a[l];if(d.classList.contains("datetime")&&!elData(d,"is-future-date")){var u=~~elData(d,"timestamp")+s,h=elData(d,"date"),p=elData(d,"time"),f=elData(d,"offset");if(elAttr(d,"title")||elAttr(d,"title",t.get("wcf.date.dateTimeFormat").replace(/%date%/,h).replace(/%time%/,p)),u>=n||n<u+60)d.textContent=t.get("wcf.date.relative.now");else if(n<u+3540){var m=Math.max(Math.round((n-u)/60),1);d.textContent=t.get("wcf.date.relative.minutes",{minutes:m})}else if(n<u+86400){var g=Math.round((n-u)/3600);d.textContent=t.get("wcf.date.relative.hours",{hours:g})}else if(n<u+518400){var v=new Date(e.getFullYear(),e.getMonth(),e.getDate()),_=Math.ceil((v/1e3-u)/86400),b=i.getTimezoneDate(1e3*u,1e3*f),w=b.getDay(),y=t.get("__days")[w];d.textContent=t.get("wcf.date.relative.pastDays",{days:_,day:y,time:p})}else d.textContent=t.get("wcf.date.shortDateTimeFormat").replace(/%date%/,h).replace(/%time%/,p)}}}}}),define("WoltLabSuite/Core/Ui/Page/Menu/Abstract",["Core","Environment","EventHandler","Language","ObjectMap","Dom/Traverse","Dom/Util","Ui/Screen"],function(e,t,i,n,a,r,o,s){"use strict";function l(e,t,i){this.init(e,t,i)}var c=elById("pageContainer"),d="";return l.prototype={init:function(e,n,r){if("packageInstallationSetup"!==elData(document.body,"template")){this._activeList=[],this._depth=0,this._enabled=!0,this._eventIdentifier=e,this._items=new a,this._menu=elById(n),this._removeActiveList=!1;var s=this.open.bind(this);this._button=elBySel(r),this._button.addEventListener(WCF_CLICK_EVENT,s),this._initItems(),this._initHeader(),i.add(this._eventIdentifier,"open",s),i.add(this._eventIdentifier,"close",this.close.bind(this)),i.add(this._eventIdentifier,"updateButtonState",this._updateButtonState.bind(this));var l,c=elByClass("menuOverlayItemList",this._menu);this._menu.addEventListener("animationend",function(){if(!this._menu.classList.contains("open"))for(var e=0,t=c.length;e<t;e++)l=c[e],l.classList.remove("active"),l.classList.remove("hidden")}.bind(this)),this._menu.children[0].addEventListener("transitionend",function(){if(this._menu.classList.add("allowScroll"),this._removeActiveList){this._removeActiveList=!1;var e=this._activeList.pop();e&&e.classList.remove("activeList")}}.bind(this));var d=elCreate("div");d.className="menuOverlayMobileBackdrop",d.addEventListener(WCF_CLICK_EVENT,this.close.bind(this)),o.insertAfter(d,this._menu),this._updateButtonState(),"android"===t.platform()&&this._initializeAndroid()}},open:function(e){return!!this._enabled&&(e instanceof Event&&e.preventDefault(),this._menu.classList.add("open"),this._menu.classList.add("allowScroll"),this._menu.children[0].classList.add("activeList"),s.scrollDisable(),c.classList.add("menuOverlay-"+this._menu.id),s.pageOverlayOpen(),!0)},close:function(e){return e instanceof Event&&e.preventDefault(),!!this._menu.classList.contains("open")&&(this._menu.classList.remove("open"),s.scrollEnable(),s.pageOverlayClose(),c.classList.remove("menuOverlay-"+this._menu.id),!0)},enable:function(){this._enabled=!0},disable:function(){this._enabled=!1,this.close(!0)},_initializeAndroid:function(){var t,i,n;switch(this._menu.id){case"pageUserMenuMobile":t="right";break;case"pageMainMenuMobile":t="left";break;default:return}i=this._menu.nextElementSibling,n=null,document.addEventListener("touchstart",function(i){var a,r,o,l;if(a=i.touches,r=this._menu.classList.contains("open"),"left"===t?(o=!r&&a[0].clientX<20,l=r&&Math.abs(this._menu.offsetWidth-a[0].clientX)<20):"right"===t&&(o=r&&Math.abs(document.body.clientWidth-this._menu.offsetWidth-a[0].clientX)<20,
+l=!r&&document.body.clientWidth-a[0].clientX<20),a.length>1)return void(d&&e.triggerEvent(document,"touchend"));if(!d&&(o||l)){if(s.pageOverlayIsActive()){for(var u=!1,h=0;h<c.classList.length;h++)c.classList[h]==="menuOverlay-"+this._menu.id&&(u=!0);if(!u)return}document.documentElement.classList.contains("redactorActive")||(n={x:a[0].clientX,y:a[0].clientY},o&&(d="left"),l&&(d="right"))}}.bind(this)),document.addEventListener("touchend",function(e){if(d&&null!==n){if(!this._menu.classList.contains("open"))return n=null,void(d="");var a;a=e?e.changedTouches[0].clientX:n.x,this._menu.classList.add("androidMenuTouchEnd"),this._menu.style.removeProperty("transform"),i.style.removeProperty(t),this._menu.addEventListener("transitionend",function(){this._menu.classList.remove("androidMenuTouchEnd")}.bind(this),{once:!0}),"left"===t?("left"===d&&a<n.x+100&&this.close(),"right"===d&&a<n.x-100&&this.close()):"right"===t&&("left"===d&&a>n.x+100&&this.close(),"right"===d&&a>n.x-100&&this.close()),n=null,d=""}}.bind(this)),document.addEventListener("touchmove",function(e){if(d&&null!==n){var a=e.touches,r=!1,o=!1;"left"===d&&(r=a[0].clientX>n.x+5),"right"===d&&(r=a[0].clientX<n.x-5),o=Math.abs(a[0].clientY-n.y)>20;var s=this._menu.classList.contains("open");if(s||!r||o||(this.open(),s=!0),s){var l=a[0].clientX;"right"===t&&(l=document.body.clientWidth-l),l>this._menu.offsetWidth&&(l=this._menu.offsetWidth),l<0&&(l=0),this._menu.style.setProperty("transform","translateX("+("left"===t?1:-1)*(l-this._menu.offsetWidth)+"px)"),i.style.setProperty(t,Math.min(this._menu.offsetWidth,l)+"px")}}}.bind(this))},_initItems:function(){elBySelAll(".menuOverlayItemLink",this._menu,this._initItem.bind(this))},_initItem:function(e){var t=e.parentNode,n=elData(t,"more");if(n)return void e.addEventListener(WCF_CLICK_EVENT,function(a){a.preventDefault(),a.stopPropagation(),i.fire(this._eventIdentifier,"more",{handler:this,identifier:n,item:e,parent:t})}.bind(this));var a,o=e.nextElementSibling;if(null!==o)if("OL"!==o.nodeName&&o.classList.contains("menuOverlayItemLinkIcon"))for(a=elCreate("span"),a.className="menuOverlayItemWrapper",t.insertBefore(a,e),a.appendChild(e);a.nextElementSibling;)a.appendChild(a.nextElementSibling);else{var s="#"!==elAttr(e,"href"),l=t.parentNode,c=elData(o,"title");this._items.set(e,{itemList:o,parentItemList:l}),""===c&&(c=r.childByClass(e,"menuOverlayItemTitle").textContent,elData(o,"title",c));var d=this._showItemList.bind(this,e);if(s){a=elCreate("span"),a.className="menuOverlayItemWrapper",t.insertBefore(a,e),a.appendChild(e);var u=elCreate("a");elAttr(u,"href","#"),u.className="menuOverlayItemLinkIcon"+(e.classList.contains("active")?" active":""),u.innerHTML='<span class="icon icon24 fa-angle-right"></span>',u.addEventListener(WCF_CLICK_EVENT,d),a.appendChild(u)}else e.classList.add("menuOverlayItemLinkMore"),e.addEventListener(WCF_CLICK_EVENT,d);var h=elCreate("li");h.className="menuOverlayHeader",a=elCreate("span"),a.className="menuOverlayItemWrapper";var p=elCreate("a");elAttr(p,"href","#"),p.className="menuOverlayItemLink menuOverlayBackLink",p.textContent=elData(l,"title"),p.addEventListener(WCF_CLICK_EVENT,this._hideItemList.bind(this,e));var f=elCreate("a");if(elAttr(f,"href","#"),f.className="menuOverlayItemLinkIcon",f.innerHTML='<span class="icon icon24 fa-times"></span>',f.addEventListener(WCF_CLICK_EVENT,this.close.bind(this)),a.appendChild(p),a.appendChild(f),h.appendChild(a),o.insertBefore(h,o.firstElementChild),!h.nextElementSibling.classList.contains("menuOverlayTitle")){var m=elCreate("li");m.className="menuOverlayTitle";var g=elCreate("span");g.textContent=c,m.appendChild(g),o.insertBefore(m,h.nextElementSibling)}}},_initHeader:function(){var e=elCreate("li");e.className="menuOverlayHeader";var t=elCreate("span");t.className="menuOverlayItemWrapper",e.appendChild(t);var i=elCreate("span");i.className="menuOverlayLogoWrapper",t.appendChild(i);var n=elCreate("span");n.className="menuOverlayLogo",n.style.setProperty("background-image",'url("'+elData(this._menu,"page-logo")+'")',""),i.appendChild(n);var a=elCreate("a");elAttr(a,"href","#"),a.className="menuOverlayItemLinkIcon",a.innerHTML='<span class="icon icon24 fa-times"></span>',a.addEventListener(WCF_CLICK_EVENT,this.close.bind(this)),t.appendChild(a);var o=r.childByClass(this._menu,"menuOverlayItemList");o.insertBefore(e,o.firstElementChild)},_hideItemList:function(e,t){t instanceof Event&&t.preventDefault(),this._menu.classList.remove("allowScroll"),this._removeActiveList=!0,this._items.get(e).parentItemList.classList.remove("hidden"),this._updateDepth(!1)},_showItemList:function(e,t){t instanceof Event&&t.preventDefault();var n=this._items.get(e),a=elData(n.itemList,"load");if(a&&!elDataBool(e,"loaded")){var r=t.currentTarget.firstElementChild;return r.classList.contains("fa-angle-right")&&(r.classList.remove("fa-angle-right"),r.classList.add("fa-spinner")),void i.fire(this._eventIdentifier,"load_"+a)}this._menu.classList.remove("allowScroll"),n.itemList.classList.add("activeList"),n.parentItemList.classList.add("hidden"),this._activeList.push(n.itemList),this._updateDepth(!0)},_updateDepth:function(e){this._depth+=e?1:-1;var t=-100*this._depth;"rtl"===n.get("wcf.global.pageDirection")&&(t*=-1),this._menu.children[0].style.setProperty("transform","translateX("+t+"%)","")},_updateButtonState:function(){var e=!1;elBySelAll(".badgeUpdate",this._menu,function(t){~~t.textContent>0&&(e=!0)}),this._button.classList[e?"add":"remove"]("pageMenuMobileButtonHasContent")}},l}),define("WoltLabSuite/Core/Ui/Page/Menu/Main",["Core","Language","Dom/Traverse","./Abstract"],function(e,t,i,n){"use strict";function a(){this.init()}var r=null,o=null,s=null,l=null,c=null;return e.inherit(a,n,{init:function(){a._super.prototype.init.call(this,"com.woltlab.wcf.MainMenuMobile","pageMainMenuMobile","#pageHeader .mainMenu"),r=elById("pageMainMenuMobilePageOptionsTitle"),null!==r&&(s=i.childByClass(r,"menuOverlayItemList"),l=elBySel(".jsPageNavigationIcons"),c=function(e){this.close(),e.stopPropagation()}.bind(this)),elAttr(this._button,"aria-label",t.get("wcf.menu.page")),elAttr(this._button,"role","button")},open:function(e){if(!a._super.prototype.open.call(this,e))return!1;if(null===r)return!0;if(o=l&&l.childElementCount>0){for(var t,i;l.childElementCount;)t=l.children[0],t.classList.add("menuOverlayItem"),t.classList.add("menuOverlayItemOption"),t.addEventListener(WCF_CLICK_EVENT,c),i=t.children[0],i.classList.add("menuOverlayItemLink"),i.classList.add("box24"),i.children[1].classList.remove("invisible"),i.children[1].classList.add("menuOverlayItemTitle"),r.parentNode.insertBefore(t,r.nextSibling);elShow(r)}else elHide(r);return!0},close:function(e){if(!a._super.prototype.close.call(this,e))return!1;if(o){elHide(r);for(var t,i=r.nextElementSibling;i&&i.classList.contains("menuOverlayItemOption");)i.classList.remove("menuOverlayItem"),i.classList.remove("menuOverlayItemOption"),i.removeEventListener(WCF_CLICK_EVENT,c),t=i.children[0],t.classList.remove("menuOverlayItemLink"),t.classList.remove("box24"),t.children[1].classList.add("invisible"),t.children[1].classList.remove("menuOverlayItemTitle"),l.appendChild(i),i=i.nextElementSibling}return!0}}),a}),define("WoltLabSuite/Core/Ui/Page/Menu/User",["Core","EventHandler","Language","./Abstract"],function(e,t,i,n){"use strict";function a(){this.init()}return e.inherit(a,n,{init:function(){var e=elBySel("#pageUserMenuMobile > .menuOverlayItemList");if(1===e.childElementCount&&e.children[0].classList.contains("menuOverlayTitle"))return void elBySel("#pageHeader .userPanel").classList.add("hideUserPanel");a._super.prototype.init.call(this,"com.woltlab.wcf.UserMenuMobile","pageUserMenuMobile","#pageHeader .userPanel"),t.add("com.woltlab.wcf.userMenu","updateBadge",function(e){elBySelAll(".menuOverlayItemBadge",this._menu,function(t){if(elData(t,"badge-identifier")===e.identifier){var i=elBySel(".badge",t);e.count?(null===i&&(i=elCreate("span"),i.className="badge badgeUpdate",t.appendChild(i)),i.textContent=e.count):null!==i&&elRemove(i),this._updateButtonState()}}.bind(this))}.bind(this)),elAttr(this._button,"aria-label",i.get("wcf.menu.user")),elAttr(this._button,"role","button")},close:function(e){var t=WCF.Dropdown.Interactive.Handler.getOpenDropdown();t?(e.preventDefault(),e.stopPropagation(),t.close()):a._super.prototype.close.call(this,e)}}),a}),define("WoltLabSuite/Core/Ui/Dropdown/Reusable",["Dictionary","Ui/SimpleDropdown"],function(e,t){"use strict";function i(e){if(!n.has(e))throw new Error("Unknown dropdown identifier '"+e+"'");return n.get(e)}var n=new e,a=0;return{init:function(e,i){if(!n.has(e)){var r=elCreate("div");r.id="reusableDropdownGhost"+a++,t.initFragment(r,i),n.set(e,r.id)}},getDropdownMenu:function(e){return t.getDropdownMenu(i(e))},registerCallback:function(e,n){t.registerCallback(i(e),n)},toggleDropdown:function(e,n){t.toggleDropdown(i(e),n)}}}),define("WoltLabSuite/Core/Ui/Mobile",["Core","Environment","EventHandler","Language","List","Dom/ChangeListener","Dom/Traverse","Ui/Alignment","Ui/CloseOverlay","Ui/Screen","./Page/Menu/Main","./Page/Menu/User","WoltLabSuite/Core/Ui/Dropdown/Reusable"],function(e,t,i,n,a,r,o,s,l,c,d,u,h){"use strict";var p=elByClass("buttonGroupNavigation"),f=null,m=null,g=null,v=!1,_=new a,b=null,w=elByClass("message"),y={},C=null,E=null,L=null,I=[],S=!1;return{setup:function(i){y=e.extend({enableMobileMenu:!0},i),b=elById("main"),elBySelAll(".sidebar",void 0,function(e){I.push(e)}),t.touch()&&document.documentElement.classList.add("touch"),"desktop"!==t.platform()&&document.documentElement.classList.add("mobile");var n=elBySel(".messageGroupList");n&&(L=elByClass("messageGroup",n)),c.on("screen-md-down",{match:this.enable.bind(this),unmatch:this.disable.bind(this),setup:this._init.bind(this)}),c.on("screen-sm-down",{match:this.enableShadow.bind(this),unmatch:this.disableShadow.bind(this),setup:this.enableShadow.bind(this)}),c.on("screen-xs",{match:this._enableSidebarXS.bind(this),unmatch:this._disableSidebarXS.bind(this),setup:this._setupSidebarXS.bind(this)})},enable:function(){v=!0,y.enableMobileMenu&&(C.enable(),E.enable())},enableShadow:function(){L&&this.rebuildShadow(L,".messageGroupLink")},disable:function(){v=!1,y.enableMobileMenu&&(C.disable(),E.disable())},disableShadow:function(){L&&this.removeShadow(L),m&&f()},_init:function(){v=!0,this._initSearchBar(),this._initButtonGroupNavigation(),this._initMessages(),this._initMobileMenu(),l.add("WoltLabSuite/Core/Ui/Mobile",this._closeAllMenus.bind(this)),r.add("WoltLabSuite/Core/Ui/Mobile",function(){this._initButtonGroupNavigation(),this._initMessages()}.bind(this))},_initSearchBar:function(){var e=elById("pageHeaderSearch"),n=elById("pageHeaderSearchInput"),a=null;i.add("com.woltlab.wcf.MainMenuMobile","more",function(i){"com.woltlab.wcf.search"===i.identifier&&(i.handler.close(!0),"ios"===t.platform()&&(a=document.body.scrollTop,c.scrollDisable()),e.style.setProperty("top",elById("pageHeader").offsetHeight+"px",""),e.classList.add("open"),n.focus(),"ios"===t.platform()&&(document.body.scrollTop=0))}),b.addEventListener(WCF_CLICK_EVENT,function(){e&&e.classList.remove("open"),"ios"===t.platform()&&null!==a&&(c.scrollEnable(),document.body.scrollTop=a,a=null)})},_initButtonGroupNavigation:function(){for(var e=0,t=p.length;e<t;e++){var i=p[e];if(!i.classList.contains("jsMobileButtonGroupNavigation")){i.classList.add("jsMobileButtonGroupNavigation");var n=elBySel(".buttonList",i);if(0!==n.childElementCount){i.parentNode.classList.add("hasMobileNavigation");var a=elCreate("a");a.className="dropdownLabel";var r=elCreate("span");r.className="icon icon24 fa-ellipsis-v",a.appendChild(r),function(e,t,i){t.addEventListener(WCF_CLICK_EVENT,function(t){t.preventDefault(),t.stopPropagation(),e.classList.toggle("open")}),i.addEventListener(WCF_CLICK_EVENT,function(t){t.stopPropagation(),e.classList.remove("open")})}(i,a,n),i.insertBefore(a,i.firstChild)}}}},_initMessages:function(){Array.prototype.forEach.call(w,function(e){if(!_.has(e)){var t=elBySel(".jsMobileNavigation",e);if(t){t.addEventListener(WCF_CLICK_EVENT,function(e){e.stopPropagation(),window.setTimeout(function(){t.classList.remove("open")},10)});var i=elBySel(".messageQuickOptions",e);i&&t.childElementCount&&(i.classList.add("active"),i.addEventListener(WCF_CLICK_EVENT,function(n){v&&"LABEL"!==n.target.nodeName&&"INPUT"!==n.target.nodeName&&(n.preventDefault(),n.stopPropagation(),this._toggleMobileNavigation(e,i,t))}.bind(this)))}_.add(e)}}.bind(this))},_initMobileMenu:function(){y.enableMobileMenu&&(C=new d,E=new u)},_closeAllMenus:function(){elBySelAll(".jsMobileButtonGroupNavigation.open, .jsMobileNavigation.open",null,function(e){e.classList.remove("open")}),v&&m&&f()},rebuildShadow:function(e,t){for(var i,n,a,r=0,s=e.length;r<s;r++)i=e[r],n=i.parentNode,null===(a=o.childByClass(n,"mobileLinkShadow"))&&elBySel(t,i).href&&(a=elCreate("a"),a.className="mobileLinkShadow",a.href=elBySel(t,i).href,n.appendChild(a),n.classList.add("mobileLinkShadowContainer"))},removeShadow:function(e){for(var t,i,n,a=0,r=e.length;a<r;a++)t=e[a],i=t.parentNode,i.classList.contains("mobileLinkShadowContainer")&&(n=o.childByClass(i,"mobileLinkShadow"),null!==n&&elRemove(n),i.classList.remove("mobileLinkShadowContainer"))},_enableSidebarXS:function(){S=!0},_disableSidebarXS:function(){S=!1,I.forEach(function(e){e.classList.remove("open")})},_setupSidebarXS:function(){I.forEach(function(e){e.addEventListener("mousedown",function(t){S&&t.target===e&&(t.preventDefault(),e.classList.toggle("open"))})}),S=!0},_toggleMobileNavigation:function(e,t,i){if(null===m)m=elCreate("ul"),m.className="dropdownMenu",h.init("com.woltlab.wcf.jsMobileNavigation",m),f=function(){m.classList.remove("dropdownOpen")};else if(m.classList.contains("dropdownOpen")&&(f(),g===e))return;m.innerHTML="",l.execute(),this._rebuildMobileNavigation(i);var n=i.previousElementSibling;if(n&&n.classList.contains("messageFooterButtonsExtra")){var a=elCreate("li");a.className="dropdownDivider",m.appendChild(a),this._rebuildMobileNavigation(n)}s.set(m,t,{horizontal:"right",allowFlip:"vertical"}),m.classList.add("dropdownOpen"),g=e},_rebuildMobileNavigation:function(t){elBySelAll(".button:not(.ignoreMobileNavigation)",t,function(t){var i=elCreate("li");t.classList.contains("active")&&(i.className="active"),i.innerHTML='<a href="#">'+elBySel("span:not(.icon)",t).textContent+"</a>",i.children[0].addEventListener(WCF_CLICK_EVENT,function(i){i.preventDefault(),i.stopPropagation(),"A"===t.nodeName?t.click():e.triggerEvent(t,WCF_CLICK_EVENT),f()}),m.appendChild(i)})}}}),define("WoltLabSuite/Core/Ui/TabMenu/Simple",["Dictionary","EventHandler","Dom/Traverse","Dom/Util"],function(e,t,i,n){"use strict";function a(t){this._container=t,this._containers=new e,this._isLegacy=null,this._store=null,this._tabs=new e}return a.prototype={validate:function(){if(!this._container.classList.contains("tabMenuContainer"))return!1;var e=i.childByTag(this._container,"NAV");if(null===e)return!1;var t=elByTag("li",e);if(0===t.length)return!1;var a,r,o,s,l=i.childrenByTag(this._container,"DIV");for(o=0,s=l.length;o<s;o++)a=l[o],r=elData(a,"name"),r||(r=n.identify(a)),elData(a,"name",r),this._containers.set(r,a);var c,d=this._container.id;for(o=0,s=t.length;o<s;o++)if(c=t[o],r=this._getTabName(c)){if(this._tabs.has(r))throw new Error("Tab names must be unique, li[data-name='"+r+"'] (tab menu id: '"+d+"') exists more than once.");if(void 0===(a=this._containers.get(r)))throw new Error("Expected content element for li[data-name='"+r+"'] (tab menu id: '"+d+"').");if(a.parentNode!==this._container)throw new Error("Expected content element '"+r+"' (tab menu id: '"+d+"') to be a direct children.");if(1!==c.childElementCount||"A"!==c.children[0].nodeName)throw new Error("Expected exactly one <a> as children for li[data-name='"+r+"'] (tab menu id: '"+d+"').");this._tabs.set(r,c)}if(!this._tabs.size)throw new Error("Expected at least one tab (tab menu id: '"+d+"').");return this._isLegacy&&(elData(this._container,"is-legacy",!0),this._tabs.forEach(function(e,t){elAttr(e,"aria-controls",t)})),!0},init:function(e){e=e||null,this._tabs.forEach(function(t){e&&e.get(elData(t,"name"))===t||t.children[0].addEventListener(WCF_CLICK_EVENT,this._onClick.bind(this))}.bind(this));var t=null;if(!e){var i=a.getIdentifierFromHash(),n=null;if(""!==i&&(n=this._tabs.get(i))&&this._container.parentNode.classList.contains("tabMenuContainer")&&(t=this._container),!n){var r=elData(this._container,"preselect")||elData(this._container,"active");"true"!==r&&r||(r=!0),!0===r?this._tabs.forEach(function(e){n||elIsHidden(e)||e.previousElementSibling&&!elIsHidden(e.previousElementSibling)||(n=e)}):"false"!==r&&(n=this._tabs.get(r))}n&&(this._containers.forEach(function(e){e.classList.add("hidden")}),this.select(null,n,!0));var o=elData(this._container,"store");if(o){var s=elCreate("input");s.type="hidden",s.name=o,s.value=elData(this.getActiveTab(),"name"),this._container.appendChild(s),this._store=s}}return t},select:function(e,i,n){if(!(i=i||this._tabs.get(e))){if(~~e==e){e=~~e;var r=0;this._tabs.forEach(function(t){r===e&&(i=t),r++})}if(!i)throw new Error("Expected a valid tab name, '"+e+"' given (tab menu id: '"+this._container.id+"').")}e=e||elData(i,"name");var o=this.getActiveTab(),s=null;if(o){var l=elData(o,"name");if(l===e)return;n||t.fire("com.woltlab.wcf.simpleTabMenu_"+this._container.id,"beforeSelect",{tab:o,tabName:l}),o.classList.remove("active"),s=this._containers.get(elData(o,"name")),s.classList.remove("active"),s.classList.add("hidden"),this._isLegacy&&(o.classList.remove("ui-state-active"),s.classList.remove("ui-state-active"))}i.classList.add("active");var c=this._containers.get(e);if(c.classList.add("active"),c.classList.remove("hidden"),this._isLegacy&&(i.classList.add("ui-state-active"),c.classList.add("ui-state-active")),this._store&&(this._store.value=e),!n){t.fire("com.woltlab.wcf.simpleTabMenu_"+this._container.id,"select",{active:i,activeName:e,previous:o,previousName:o?elData(o,"name"):null});var d=this._isLegacy&&"function"==typeof window.jQuery?window.jQuery:null;d&&d(this._container).trigger("wcftabsbeforeactivate",{newTab:d(i),oldTab:d(o),newPanel:d(c),oldPanel:d(s)});var u=window.location.href.replace(/#+[^#]*$/,"");a.getIdentifierFromHash()===e?u+=window.location.hash:u+="#"+e,window.history.replaceState(void 0,void 0,u)}require(["WoltLabSuite/Core/Ui/TabMenu"],function(e){e.scrollToTab(i)})},selectFirstVisible:function(){var e;return this._tabs.forEach(function(t){e||elIsHidden(t)||(e=t)}.bind(this)),e&&this.select(void 0,e,!1),!!e},rebuild:function(){var t=new e;t.merge(this._tabs),this.validate(),this.init(t)},hasTab:function(e){return this._tabs.has(e)},_onClick:function(e){e.preventDefault(),this.select(null,e.currentTarget.parentNode)},_getTabName:function(e){var t=elData(e,"name");return t||1===e.childElementCount&&"A"===e.children[0].nodeName&&e.children[0].href.match(/#([^#]+)$/)&&(t=RegExp.$1,null===elById(t)?t=null:(this._isLegacy=!0,elData(e,"name",t))),t},getActiveTab:function(){return elBySel("#"+this._container.id+" > nav > ul > li.active")},getContainers:function(){return this._containers},getTabs:function(){return this._tabs}},a.getIdentifierFromHash=function(){return window.location.hash.match(/^#+([^\/]+)+(?:\/.+)?/)?RegExp.$1:""},a}),define("WoltLabSuite/Core/Ui/TabMenu",["Dictionary","EventHandler","Dom/ChangeListener","Dom/Util","Ui/CloseOverlay","Ui/Screen","./TabMenu/Simple"],function(e,t,i,n,a,r,o){"use strict";var s=null,l=!1,c=new e;return{setup:function(){this._init(),this._selectErroneousTabs(),i.add("WoltLabSuite/Core/Ui/TabMenu",this._init.bind(this)),a.add("WoltLabSuite/Core/Ui/TabMenu",function(){s&&(s.classList.remove("active"),s=null)}),r.on("screen-sm-down",{enable:this._scrollEnable.bind(this,!1),disable:this._scrollDisable.bind(this),setup:this._scrollEnable.bind(this,!0)}),window.addEventListener("hashchange",function(){var e=o.getIdentifierFromHash(),t=e?elById(e):null;null!==t&&t.classList.contains("tabMenuContent")&&c.forEach(function(t){t.hasTab(e)&&t.select(e)})});var e=o.getIdentifierFromHash();e&&window.setTimeout(function(){var t=elById(e);if(t&&t.classList.contains("tabMenuContent")){var i=window.scrollY||window.pageYOffset;if(i>0){var a=t.parentNode,r=a.offsetTop-50;if(r<0&&(r=0),i>r){var o=n.offset(a).top;o<=50?o=0:o-=50,window.scrollTo(0,o)}}}},100)},_init:function(){for(var e,t,i,a,r,l=elBySelAll(".tabMenuContainer:not(.staticTabMenuContainer)"),d=0,u=l.length;d<u;d++)e=l[d],t=n.identify(e),c.has(t)||(r=new o(e),r.validate()&&(a=r.init(),c.set(t,r),a instanceof Element&&(r=this.getTabMenu(a.parentNode.id),r.select(a.id,null,!0)),i=elBySel("#"+t+" > nav > ul"),function(e){e.addEventListener(WCF_CLICK_EVENT,function(t){t.preventDefault(),t.stopPropagation(),t.target===e?(e.classList.add("active"),s=e):(e.classList.remove("active"),s=null)})}(i),elBySelAll(".tabMenu, .menu",e,function(e){var t=this._rebuildMenuOverflow.bind(this,e),i=null;elBySel("ul",e).addEventListener("scroll",function(){null!==i&&window.clearTimeout(i),i=window.setTimeout(t,10)})}.bind(this))))},_selectErroneousTabs:function(){c.forEach(function(e){var t=!1;e.getContainers().forEach(function(i){!t&&elByClass("formError",i).length&&(t=!0,e.select(i.id))})})},getTabMenu:function(e){return c.get(e)},_scrollEnable:function(e){l=!0,c.forEach(function(t){var i=t.getActiveTab();e?this._rebuildMenuOverflow(i.closest(".menu, .tabMenu")):this.scrollToTab(i)}.bind(this))},_scrollDisable:function(){l=!1},scrollToTab:function(e){if(l){var t=e.closest("ul"),i=t.clientWidth,n=t.scrollLeft,a=t.scrollWidth;if(i!==a){var r=e.offsetLeft,o=!1;r<n&&(o=!0);var s=!1;if(!o){var c=i-(r-n),d=e.clientWidth;null!==e.nextElementSibling&&(s=!0,d+=20),c<d&&(o=!0)}o&&this._scrollMenu(t,r,n,a,i,s)}}},_scrollMenu:function(e,t,i,n,a,r){r?t-=15:t>0&&(t-=15),t=t<0?0:Math.min(t,n-a),i!==t&&(e.classList.add("enableAnimation"),i<t?e.firstElementChild.style.setProperty("margin-left",i-t+"px",""):e.style.setProperty("padding-left",i-t+"px",""),setTimeout(function(){e.classList.remove("enableAnimation"),e.firstElementChild.style.removeProperty("margin-left"),e.style.removeProperty("padding-left"),e.scrollLeft=t},300))},_rebuildMenuOverflow:function(e){if(l){var t=e.clientWidth,i=elBySel("ul",e),n=i.scrollLeft,a=i.scrollWidth,r=n>0,o=elBySel(".tabMenuOverlayLeft",e);r?(null===o&&(o=elCreate("span"),o.className="tabMenuOverlayLeft icon icon24 fa-angle-left",o.addEventListener(WCF_CLICK_EVENT,function(){var e=i.clientWidth;this._scrollMenu(i,i.scrollLeft-~~(e/2),i.scrollLeft,i.scrollWidth,e,0)}.bind(this)),e.insertBefore(o,e.firstChild)),o.classList.add("active")):null!==o&&o.classList.remove("active");var s=t+n<a,c=elBySel(".tabMenuOverlayRight",e);s?(null===c&&(c=elCreate("span"),c.className="tabMenuOverlayRight icon icon24 fa-angle-right",c.addEventListener(WCF_CLICK_EVENT,function(){var e=i.clientWidth;this._scrollMenu(i,i.scrollLeft+~~(e/2),i.scrollLeft,i.scrollWidth,e,0)}.bind(this)),e.appendChild(c)),c.classList.add("active")):null!==c&&c.classList.remove("active")}}}}),define("WoltLabSuite/Core/Ui/FlexibleMenu",["Core","Dictionary","Dom/ChangeListener","Dom/Traverse","Dom/Util","Ui/SimpleDropdown"],function(e,t,i,n,a,r){"use strict";var o=new t,s=new t,l=new t,c=new t;return{setup:function(){null!==elById("mainMenu")&&this.register("mainMenu");var e=elBySel(".navigationHeader");null!==e&&this.register(a.identify(e)),window.addEventListener("resize",this.rebuildAll.bind(this)),i.add("WoltLabSuite/Core/Ui/FlexibleMenu",this.registerTabMenus.bind(this))},register:function(e){var t=elById(e);if(null===t)throw"Expected a valid element id, '"+e+"' does not exist.";if(!o.has(e)){var i=n.childByTag(t,"UL");if(null===i)throw"Expected an <ul> element as child of container '"+e+"'.";o.set(e,t),c.set(e,i),this.rebuild(e)}},registerTabMenus:function(){for(var e=elBySelAll(".tabMenuContainer:not(.jsFlexibleMenuEnabled), .messageTabMenu:not(.jsFlexibleMenuEnabled)"),t=0,i=e.length;t<i;t++){var r=e[t],o=n.childByTag(r,"NAV");null!==o&&(r.classList.add("jsFlexibleMenuEnabled"),this.register(a.identify(o)))}},rebuildAll:function(){o.forEach(function(e,t){this.rebuild(t)}.bind(this))},rebuild:function(t){var i=o.get(t);if(void 0===i)throw"Expected a valid element id, '"+t+"' is unknown.";var d=window.getComputedStyle(i),u=i.parentNode.clientWidth;u-=a.styleAsInt(d,"margin-left"),u-=a.styleAsInt(d,"margin-right");var h=c.get(t),p=n.childrenByTag(h,"LI"),f=s.get(t),m=0;if(void 0!==f){for(var g=0,v=p.length;g<v;g++){var _=p[g];_.classList.contains("dropdown")||elShow(_)}null!==f.parentNode&&(m=a.outerWidth(f))}var b=h.scrollWidth-m,w=[];if(b>u)for(var g=p.length-1;g>=0;g--){var _=p[g];if(!(_.classList.contains("dropdown")||_.classList.contains("active")||_.classList.contains("ui-state-active"))&&(w.push(_),elHide(_),h.scrollWidth<u))break}if(w.length){var y;if(void 0===f){f=elCreate("li"),f.className="dropdown jsFlexibleMenuDropdown";var C=elCreate("a");C.className="icon icon16 fa-list",f.appendChild(C),y=elCreate("ul"),y.classList.add("dropdownMenu"),f.appendChild(y),s.set(t,f),l.set(t,y),r.init(C)}else y=l.get(t);null===f.parentNode&&h.appendChild(f);var E=document.createDocumentFragment(),L=this;w.forEach(function(i){var n=elCreate("li");n.innerHTML=i.innerHTML,n.addEventListener(WCF_CLICK_EVENT,function(n){n.preventDefault(),e.triggerEvent(elBySel("a",i),WCF_CLICK_EVENT),setTimeout(function(){L.rebuild(t)},59)}.bind(this)),E.appendChild(n)}),y.innerHTML="",y.appendChild(E)}else void 0!==f&&null!==f.parentNode&&elRemove(f)}}}),define("WoltLabSuite/Core/Ui/Tooltip",["Environment","Dom/ChangeListener","Ui/Alignment"],function(e,t,i){"use strict";var n=null,a=null,r=null,o=null,s=null,l=null;return{setup:function(){"desktop"===e.platform()&&(l=elCreate("div"),elAttr(l,"id","balloonTooltip"),l.classList.add("balloonTooltip"),l.addEventListener("transitionend",function(){l.classList.contains("active")||["bottom","left","right","top"].forEach(function(e){l.style.removeProperty(e)})}),s=elCreate("span"),elAttr(s,"id","balloonTooltipText"),l.appendChild(s),o=elCreate("span"),o.classList.add("elementPointer"),o.appendChild(elCreate("span")),l.appendChild(o),document.body.appendChild(l),r=elByClass("jsTooltip"),n=this._mouseEnter.bind(this),a=this._mouseLeave.bind(this),this.init(),t.add("WoltLabSuite/Core/Ui/Tooltip",this.init.bind(this)),window.addEventListener("scroll",this._mouseLeave.bind(this)))},init:function(){0!==r.length&&elBySelAll(".jsTooltip",void 0,function(e){e.classList.remove("jsTooltip");var t=elAttr(e,"title").trim();t.length&&(elData(e,"tooltip",t),e.removeAttribute("title"),elAttr(e,"aria-label",t),e.addEventListener("mouseenter",n),e.addEventListener("mouseleave",a),e.addEventListener(WCF_CLICK_EVENT,a))})},_mouseEnter:function(e){var t=e.currentTarget,n=elAttr(t,"title");if(n="string"==typeof n?n.trim():"",""!==n&&(elData(t,"tooltip",n),t.removeAttribute("title")),n=elData(t,"tooltip"),l.style.removeProperty("top"),l.style.removeProperty("left"),!n.length)return void l.classList.remove("active");l.classList.add("active"),s.textContent=n,i.set(l,t,{horizontal:"center",verticalOffset:4,pointer:!0,pointerClassNames:["inverse"],vertical:"top"})},_mouseLeave:function(){l.classList.remove("active")}}}),define("WoltLabSuite/Core/Date/Picker",["DateUtil","Dom/Traverse","Dom/Util","EventHandler","Language","ObjectMap","Dom/ChangeListener","Ui/Alignment","WoltLabSuite/Core/Ui/CloseOverlay"],function(e,t,i,n,a,r,o,s,l){"use strict";var c=!1,d=0,u=!1,h=new r,p=null,f=0,m=0,g=[],v=null,_=null,b=null,w=null,y=null,C=null,E=null,L=null,I=null,S=null,A=null,D={init:function(){this._setup();for(var t=elBySelAll('input[type="date"]:not(.inputDatePicker), input[type="datetime"]:not(.inputDatePicker)'),i=new Date,n=0,r=t.length;n<r;n++){var o=t[n];o.classList.add("inputDatePicker"),o.readOnly=!0;var s="datetime"===elAttr(o,"type"),l=s&&elDataBool(o,"time-only"),c=elDataBool(o,"disable-clear"),d=s&&elDataBool(o,"ignore-timezone"),u=o.classList.contains("birthday");elData(o,"is-date-time",s),elData(o,"is-time-only",l);var p=null,f=elAttr(o,"value"),m=/^\d+-\d+-\d+$/.test(f);if(elAttr(o,"value")){if(l){p=new Date;var g=f.split(":");p.setHours(g[0],g[1])}else{if(d||u||m){var v=new Date(f).getTimezoneOffset(),_=v>0?"-":"+";v=Math.abs(v);var b=Math.floor(v/60).toString(),w=(v%60).toString();_+=2===b.length?b:"0"+b,_+=":",_+=2===w.length?w:"0"+w,u||m?f+="T00:00:00"+_:f=f.replace(/[+-][0-9]{2}:[0-9]{2}$/,_)}p=new Date(f)}var y=p.getTime();if(isNaN(y))f="";else{elData(o,"value",y);f=e[l?"formatTime":"formatDate"+(s?"Time":"")](p)}}var C=0===f.length;if(u?(elData(o,"min-date","120"),elData(o,"max-date",(new Date).getFullYear()+"-12-31")):(o.min&&elData(o,"min-date",o.min),o.max&&elData(o,"max-date",o.max)),this._initDateRange(o,i,!0),this._initDateRange(o,i,!1),elData(o,"min-date")===elData(o,"max-date"))throw new Error("Minimum and maximum date cannot be the same (element id '"+o.id+"').");o.type="text",o.value=f,elData(o,"empty",C),elData(o,"placeholder")&&elAttr(o,"placeholder",elData(o,"placeholder"));var E=elCreate("input");if(E.id=o.id+"DatePicker",E.name=o.name,E.type="hidden",null!==p&&(E.value=l?e.format(p,"H:i"):d?e.format(p,"Y-m-dTH:i:s"):e.format(p,s?"c":"Y-m-d")),o.parentNode.insertBefore(E,o),o.removeAttribute("name"),o.addEventListener(WCF_CLICK_EVENT,S),!o.disabled){var L=elCreate("div");L.className="inputAddon";var I=elCreate("a");I.className="inputSuffix button jsTooltip",I.href="#",elAttr(I,"role","button"),elAttr(I,"tabindex","0"),elAttr(I,"title",a.get("wcf.date.datePicker")),elAttr(I,"aria-label",a.get("wcf.date.datePicker")),elAttr(I,"aria-haspopup",!0),elAttr(I,"aria-expanded",!1),I.addEventListener(WCF_CLICK_EVENT,S),L.appendChild(I);var A=elCreate("span");A.className="icon icon16 fa-calendar",I.appendChild(A),o.parentNode.insertBefore(L,o),L.insertBefore(o,I),c||(I=elCreate("a"),I.className="inputSuffix button",I.addEventListener(WCF_CLICK_EVENT,this.clear.bind(this,o)),C&&I.style.setProperty("visibility","hidden",""),L.appendChild(I),A=elCreate("span"),A.className="icon icon16 fa-times",I.appendChild(A))}for(var D=!1,x=["tiny","short","medium","long"],T=0;T<4;T++)o.classList.contains(x[T])&&(D=!0);D||o.classList.add("short"),h.set(o,{clearButton:I,shadow:E,disableClear:c,isDateTime:s,isEmpty:C,isTimeOnly:l,ignoreTimezone:d,onClose:null})}},_initDateRange:function(e,t,i){var n="data-"+(i?"min":"max")+"-date",a=e.hasAttribute(n)?elAttr(e,n).trim():"";if(a.match(/^(\d{4})-(\d{2})-(\d{2})$/))a=new Date(a).getTime();else if("now"===a)a=t.getTime();else if(a.match(/^\d{1,3}$/)){var r=new Date(t.getTime());r.setFullYear(r.getFullYear()+~~a*(i?-1:1)),a=r.getTime()}else if(a.match(/^datePicker-(.+)$/)){if(a=RegExp.$1,null===elById(a))throw new Error("Reference date picker identified by '"+a+"' does not exists (element id: '"+e.id+"').")}else a=/^\d{4}\-\d{2}\-\d{2}T/.test(a)?new Date(a).getTime():new Date(i?1902:2038,0,1).getTime();elAttr(e,n,a)},_setup:function(){c||(c=!0,d=~~a.get("wcf.date.firstDayOfTheWeek"),S=this._open.bind(this),o.add("WoltLabSuite/Core/Date/Picker",this.init.bind(this)),l.add("WoltLabSuite/Core/Date/Picker",this._close.bind(this)))},_open:function(e){e.preventDefault(),e.stopPropagation(),this._createPicker(),null===A&&(A=this._maintainFocus.bind(this),document.body.addEventListener("focus",A,{capture:!0}));var i="INPUT"===e.currentTarget.nodeName?e.currentTarget:e.currentTarget.previousElementSibling;if(i===p)return void this._close();var n=t.parentByClass(i,"dialogContent");null!==n&&(elDataBool(n,"has-datepicker-scroll-listener")||(n.addEventListener("scroll",this._onDialogScroll.bind(this)),elData(n,"has-datepicker-scroll-listener",1))),p=i;var a,r=h.get(p),o=elData(p,"value");o?(a=new Date(+o),"Invalid Date"===a.toString()&&(a=new Date)):a=new Date,m=elData(p,"min-date"),
+m.match(/^datePicker-(.+)$/)&&(m=elData(elById(RegExp.$1),"value")),m=new Date(+m),f=elData(p,"max-date"),f.match(/^datePicker-(.+)$/)&&(f=elData(elById(RegExp.$1),"value")),f=new Date(+f),r.isDateTime?(_.value=a.getHours(),b.value=a.getMinutes(),I.classList.add("datePickerTime")):I.classList.remove("datePickerTime"),I.classList[r.isTimeOnly?"add":"remove"]("datePickerTimeOnly"),this._renderPicker(a.getDate(),a.getMonth(),a.getFullYear()),s.set(I,p),elAttr(p.nextElementSibling,"aria-expanded",!0),u=!1},_close:function(){if(null!==I&&I.classList.contains("active")){I.classList.remove("active");var e=h.get(p);"function"==typeof e.onClose&&e.onClose(),n.fire("WoltLabSuite/Core/Date/Picker","close",{element:p}),elAttr(p.nextElementSibling,"aria-expanded",!1),p=null,m=0,f=0}},_onDialogScroll:function(e){if(null!==p){var t=e.currentTarget,n=i.offset(p),a=i.offset(t);n.top+p.clientHeight<=a.top?this._close():n.top>=a.top+t.offsetHeight?this._close():n.left<=a.left?this._close():n.left>=a.left+t.offsetWidth?this._close():s.set(I,p)}},_renderPicker:function(e,t,i){this._renderGrid(e,t,i);for(var n="",a=m.getFullYear(),r=f.getFullYear();a<=r;a++)n+='<option value="'+a+'">'+a+"</option>";L.innerHTML=n,L.value=i,w.value=t,I.classList.add("active")},_renderGrid:function(t,i,n){var a,r,o=void 0!==t,s=void 0!==i;if(t=~~t||~~elData(v,"day"),i=~~i,n=~~n,s||n){var l=0!==n,c=document.createDocumentFragment();c.appendChild(v),s||(i=~~elData(v,"month")),n=n||~~elData(v,"year");var u=new Date(n+"-"+("0"+(i+1).toString()).slice(-2)+"-"+("0"+t.toString()).slice(-2));for(u<m?(n=m.getFullYear(),i=m.getMonth(),t=m.getDate(),w.value=i,L.value=n,l=!0):u>f&&(n=f.getFullYear(),i=f.getMonth(),t=f.getDate(),w.value=i,L.value=n,l=!0),u=new Date(n+"-"+("0"+(i+1).toString()).slice(-2)+"-01");u.getDay()!==d;)u.setDate(u.getDate()-1);elShow(g[35].parentNode);var h,p=new Date(m.getFullYear(),m.getMonth(),m.getDate());for(r=0;r<42;r++){if(35===r&&u.getMonth()!==i){elHide(g[35].parentNode);break}a=g[r],a.textContent=u.getDate(),h=u.getMonth()===i,h&&(u<p?h=!1:u>f&&(h=!1)),a.classList[h?"remove":"add"]("otherMonth"),h&&(a.href="#",elAttr(a,"role","button"),elAttr(a,"tabindex","0"),elAttr(a,"title",e.formatDate(u)),elAttr(a,"aria-label",e.formatDate(u))),u.setDate(u.getDate()+1)}if(elData(v,"month",i),elData(v,"year",n),I.insertBefore(c,E),!o&&(u=new Date(n,i,t),u.getDate()!==t)){for(;u.getMonth()!==i;)u.setDate(u.getDate()-1);t=u.getDate()}if(l){for(r=0;r<12;r++){var _=w.children[r];_.disabled=n===m.getFullYear()&&_.value<m.getMonth()||n===f.getFullYear()&&_.value>f.getMonth()}var b=new Date(n+"-"+("0"+(i+1).toString()).slice(-2)+"-01");b.setMonth(b.getMonth()+1),y.classList[b<f?"add":"remove"]("active");var S=new Date(n+"-"+("0"+(i+1).toString()).slice(-2)+"-01");S.setDate(S.getDate()-1),C.classList[S>m?"add":"remove"]("active")}}if(t){for(r=0;r<35;r++)a=g[r],a.classList[a.classList.contains("otherMonth")||~~a.textContent!==t?"remove":"add"]("active");elData(v,"day",t)}this._formatValue()},_formatValue:function(){var e,t=h.get(p);"true"!==elData(p,"empty")&&(e=t.isDateTime?new Date(elData(v,"year"),elData(v,"month"),elData(v,"day"),_.value,b.value):new Date(elData(v,"year"),elData(v,"month"),elData(v,"day")),this.setDate(p,e))},_createPicker:function(){if(null===I){I=elCreate("div"),I.className="datePicker",I.addEventListener(WCF_CLICK_EVENT,function(e){e.stopPropagation()});var t=elCreate("header");I.appendChild(t),C=elCreate("a"),C.className="previous jsTooltip",C.href="#",elAttr(C,"role","button"),elAttr(C,"tabindex","0"),elAttr(C,"title",a.get("wcf.date.datePicker.previousMonth")),elAttr(C,"aria-label",a.get("wcf.date.datePicker.previousMonth")),C.innerHTML='<span class="icon icon16 fa-arrow-left"></span>',C.addEventListener(WCF_CLICK_EVENT,this.previousMonth.bind(this)),t.appendChild(C);var i=elCreate("span");t.appendChild(i),w=elCreate("select"),w.className="month jsTooltip",elAttr(w,"title",a.get("wcf.date.datePicker.month")),elAttr(w,"aria-label",a.get("wcf.date.datePicker.month")),w.addEventListener("change",this._changeMonth.bind(this)),i.appendChild(w);var n,r="",o=a.get("__monthsShort");for(n=0;n<12;n++)r+='<option value="'+n+'">'+o[n]+"</option>";w.innerHTML=r,L=elCreate("select"),L.className="year jsTooltip",elAttr(L,"title",a.get("wcf.date.datePicker.year")),elAttr(L,"aria-label",a.get("wcf.date.datePicker.year")),L.addEventListener("change",this._changeYear.bind(this)),i.appendChild(L),y=elCreate("a"),y.className="next jsTooltip",y.href="#",elAttr(y,"role","button"),elAttr(y,"tabindex","0"),elAttr(y,"title",a.get("wcf.date.datePicker.nextMonth")),elAttr(y,"aria-label",a.get("wcf.date.datePicker.nextMonth")),y.innerHTML='<span class="icon icon16 fa-arrow-right"></span>',y.addEventListener(WCF_CLICK_EVENT,this.nextMonth.bind(this)),t.appendChild(y),v=elCreate("ul"),I.appendChild(v);var s=elCreate("li");s.className="weekdays",v.appendChild(s);var l,c=a.get("__daysShort");for(n=0;n<7;n++){var u=n+d;u>6&&(u-=7),l=elCreate("span"),l.textContent=c[u],s.appendChild(l)}var h,p,f=this._click.bind(this);for(n=0;n<6;n++){p=elCreate("li"),v.appendChild(p);for(var m=0;m<7;m++)h=elCreate("a"),h.addEventListener(WCF_CLICK_EVENT,f),g.push(h),p.appendChild(h)}E=elCreate("footer"),I.appendChild(E),_=elCreate("select"),_.className="hour",elAttr(_,"title",a.get("wcf.date.datePicker.hour")),elAttr(_,"aria-label",a.get("wcf.date.datePicker.hour")),_.addEventListener("change",this._formatValue.bind(this));var S="",A=new Date(2e3,0,1),D=a.get("wcf.date.timeFormat").replace(/:/,"").replace(/[isu]/g,"");for(n=0;n<24;n++)A.setHours(n),S+='<option value="'+n+'">'+e.format(A,D)+"</option>";for(_.innerHTML=S,E.appendChild(_),E.appendChild(document.createTextNode(" : ")),b=elCreate("select"),b.className="minute",elAttr(b,"title",a.get("wcf.date.datePicker.minute")),elAttr(b,"aria-label",a.get("wcf.date.datePicker.minute")),b.addEventListener("change",this._formatValue.bind(this)),S="",n=0;n<60;n++)S+='<option value="'+n+'">'+(n<10?"0"+n.toString():n)+"</option>";b.innerHTML=S,E.appendChild(b),document.body.appendChild(I)}},previousMonth:function(e){e.preventDefault(),"0"===w.value?(w.value=11,L.value=~~L.value-1):w.value=~~w.value-1,this._renderGrid(void 0,w.value,L.value)},nextMonth:function(e){e.preventDefault(),"11"===w.value?(w.value=0,L.value=1+~~L.value):w.value=1+~~w.value,this._renderGrid(void 0,w.value,L.value)},_changeMonth:function(e){this._renderGrid(void 0,e.currentTarget.value)},_changeYear:function(e){this._renderGrid(void 0,void 0,e.currentTarget.value)},_click:function(e){if(e.preventDefault(),!e.currentTarget.classList.contains("otherMonth")){elData(p,"empty",!1),this._renderGrid(e.currentTarget.textContent);h.get(p).isDateTime||this._close()}},getDate:function(e){return e=this._getElement(e),e.hasAttribute("data-value")?new Date(+elData(e,"value")):null},setDate:function(t,i){t=this._getElement(t);var n=h.get(t);elData(t,"value",i.getTime());var a,r="";n.isDateTime?n.isTimeOnly?(a=e.formatTime(i),r="H:i"):n.ignoreTimezone?(a=e.formatDateTime(i),r="Y-m-dTH:i:s"):(a=e.formatDateTime(i),r="c"):(a=e.formatDate(i),r="Y-m-d"),t.value=a,n.shadow.value=e.format(i,r),n.disableClear||n.clearButton.style.removeProperty("visibility")},getValue:function(e){e=this._getElement(e);var t=h.get(e);return t?t.shadow.value:""},clear:function(e){e=this._getElement(e);var t=h.get(e);e.removeAttribute("data-value"),e.value="",t.disableClear||t.clearButton.style.setProperty("visibility","hidden",""),t.isEmpty=!0,t.shadow.value=""},destroy:function(e){e=this._getElement(e);var t=h.get(e),i=e.parentNode;i.parentNode.insertBefore(e,i),elRemove(i),elAttr(e,"type","date"+(t.isDateTime?"time":"")),e.name=t.shadow.name,e.value=t.shadow.value,e.removeAttribute("data-value"),e.removeEventListener(WCF_CLICK_EVENT,S),elRemove(t.shadow),e.classList.remove("inputDatePicker"),e.readOnly=!1,h.delete(e)},setCloseCallback:function(e,t){e=this._getElement(e),h.get(e).onClose=t},_getElement:function(e){if("string"==typeof e&&(e=elById(e)),!(e instanceof Element&&e.classList.contains("inputDatePicker")&&h.has(e)))throw new Error("Expected a valid date picker input element or id.");return e},_maintainFocus:function(e){null!==I&&I.classList.contains("active")&&(I.contains(e.target)?u=!0:u?(p.nextElementSibling.focus(),u=!1):elBySel(".previous",I).focus())}};return window.__wcf_bc_datePicker=D,D}),define("WoltLabSuite/Core/Ui/Page/Action",["Dictionary","Dom/Util"],function(e,t){"use strict";var i=new e,n=null,a=!1;return{setup:function(){a=!0,n=elCreate("ul"),n.className="pageAction",document.body.appendChild(n)},add:function(e,r,o){!1===a&&this.setup();var s=elCreate("li");if(r.classList.add("button"),r.classList.add("buttonPrimary"),s.appendChild(r),elAttr(s,"aria-hidden","toTop"===e?"true":"false"),elData(s,"name",e),"toTop"===e)s.className="toTop initiallyHidden",n.appendChild(s);else{var l=null;o&&void 0!==(l=i.get(o))&&(l=l.parentNode),null===l&&n.childElementCount&&(l=n.children[0]),null===l?t.prepend(s,n):n.insertBefore(s,l)}i.set(e,r),this._renderContainer()},has:function(e){return i.has(e)},get:function(e){return i.get(e)},remove:function(e){var t=i.get(e);if(void 0!==t){var a=t.parentNode;a.addEventListener("animationend",function(){try{n.removeChild(a),i.delete(e)}catch(e){}}),this.hide(e)}},hide:function(e){var t=i.get(e);t&&(elAttr(t.parentNode,"aria-hidden","true"),this._renderContainer())},show:function(e){var t=i.get(e);t&&(t.parentNode.classList.contains("initiallyHidden")&&t.parentNode.classList.remove("initiallyHidden"),elAttr(t.parentNode,"aria-hidden","false"),this._renderContainer())},_renderContainer:function(){var e=!1;if(n.childElementCount)for(var t=0,i=n.childElementCount;t<i;t++)if("false"===elAttr(n.children[t],"aria-hidden")){e=!0;break}n.classList[e?"add":"remove"]("active")}}}),define("WoltLabSuite/Core/Ui/Page/JumpToTop",["Environment","Language","./Action"],function(e,t,i){"use strict";function n(){this.init()}return n.prototype={init:function(){if("desktop"===e.platform()){this._callbackScrollEnd=this._afterScroll.bind(this),this._timeoutScroll=null;var n=elCreate("a");n.className="jsTooltip",n.href="#",elAttr(n,"title",t.get("wcf.global.scrollUp")),elAttr(n,"role","button"),n.innerHTML='<span class="icon icon32 fa-angle-up"></span>',n.addEventListener(WCF_CLICK_EVENT,this._jump.bind(this)),i.add("toTop",n),window.addEventListener("scroll",this._scroll.bind(this)),this._afterScroll()}},_jump:function(e){e.preventDefault(),elById("top").scrollIntoView({behavior:"smooth"})},_scroll:function(){null!==this._timeoutScroll&&window.clearTimeout(this._timeoutScroll),this._timeoutScroll=window.setTimeout(this._callbackScrollEnd,100)},_afterScroll:function(){this._timeoutScroll=null,i[window.pageYOffset>=300?"show":"hide"]("toTop")}},n}),define("WoltLabSuite/Core/Bootstrap",["favico","enquire","perfect-scrollbar","WoltLabSuite/Core/Date/Time/Relative","Ui/SimpleDropdown","WoltLabSuite/Core/Ui/Mobile","WoltLabSuite/Core/Ui/TabMenu","WoltLabSuite/Core/Ui/FlexibleMenu","Ui/Dialog","WoltLabSuite/Core/Ui/Tooltip","WoltLabSuite/Core/Language","WoltLabSuite/Core/Environment","WoltLabSuite/Core/Date/Picker","EventHandler","Core","WoltLabSuite/Core/Ui/Page/JumpToTop","Devtools","Dom/ChangeListener"],function(e,t,i,n,a,r,o,s,l,c,d,u,h,p,f,m,g,v){"use strict";return window.Favico=e,window.enquire=t,null==window.WCF&&(window.WCF={}),null==window.WCF.Language&&(window.WCF.Language={}),window.WCF.Language.get=d.get,window.WCF.Language.add=d.add,window.WCF.Language.addObject=d.addObject,window.__wcf_bc_eventHandler=p,{setup:function(e){e=f.extend({enableMobileMenu:!0},e),window.ENABLE_DEVELOPER_TOOLS&&g._internal_.enable(),u.setup(),n.setup(),h.init(),a.setup(),r.setup({enableMobileMenu:e.enableMobileMenu}),o.setup(),l.setup(),c.setup();for(var t=elBySelAll("form[method=get]"),i=0,s=t.length;i<s;i++)t[i].setAttribute("method","post");"microsoft"===u.browser()&&(window.onbeforeunload=function(){});var d=0;d=window.setInterval(function(){"function"==typeof window.jQuery&&(window.clearInterval(d),window.jQuery(function(){new m}),window.jQuery.holdReady(!1))},20),this._initA11y(),v.add("WoltLabSuite/Core/Bootstrap",this._initA11y.bind(this))},_initA11y:function(){elBySelAll("nav:not([aria-label]):not([aria-labelledby]):not([role])",void 0,function(e){elAttr(e,"role","presentation")}),elBySelAll("article:not([aria-label]):not([aria-labelledby]):not([role])",void 0,function(e){elAttr(e,"role","presentation")})}}}),define("WoltLabSuite/Core/Controller/Style/Changer",["Ajax","Language","Ui/Dialog"],function(e,t,i){"use strict";return{setup:function(){var e=elBySel(".jsButtonStyleChanger");e&&e.addEventListener(WCF_CLICK_EVENT,this.showDialog.bind(this))},showDialog:function(e){e.preventDefault(),i.open(this)},_dialogSetup:function(){return{id:"styleChanger",options:{disableContentPadding:!0,title:t.get("wcf.style.changeStyle")},source:{data:{actionName:"getStyleChooser",className:"wcf\\data\\style\\StyleAction"},after:function(e){for(var t=elBySelAll(".styleList > li",e),i=0,n=t.length;i<n;i++){var a=t[i];a.classList.add("pointer"),a.addEventListener(WCF_CLICK_EVENT,this._click.bind(this))}}.bind(this)}}},_click:function(t){t.preventDefault(),e.apiOnce({data:{actionName:"changeStyle",className:"wcf\\data\\style\\StyleAction",objectIDs:[elData(t.currentTarget,"style-id")]},success:function(){window.location.reload()}})}}}),define("WoltLabSuite/Core/Controller/Popover",["Ajax","Dictionary","Environment","Dom/ChangeListener","Dom/Util","Ui/Alignment"],function(e,t,i,n,a,r){"use strict";var o=null,s=new t,l=new t,c=new t,d=null,u=!1,h=null,p=null,f=null,m=null,g=null,v=null,_=null,b=null;return{_setup:function(){if(null===f){f=elCreate("div"),f.className="popover forceHide",m=elCreate("div"),m.className="popoverContent",f.appendChild(m);var e=elCreate("span");e.className="elementPointer",e.appendChild(elCreate("span")),f.appendChild(e),document.body.appendChild(f),g=this._hide.bind(this),_=this._mouseEnter.bind(this),b=this._mouseLeave.bind(this),f.addEventListener("mouseenter",this._popoverMouseEnter.bind(this)),f.addEventListener("mouseleave",b),f.addEventListener("animationend",this._clearContent.bind(this)),window.addEventListener("beforeunload",function(){u=!0,null!==h&&window.clearTimeout(h),this._hide(!0)}.bind(this)),n.add("WoltLabSuite/Core/Controller/Popover",this._init.bind(this))}},init:function(e){"desktop"===i.platform()&&(e.attributeName=e.attributeName||"data-object-id",e.legacy=!0===e.legacy,this._setup(),c.has(e.identifier)||(c.set(e.identifier,{attributeName:e.attributeName,elements:e.legacy?e.className:elByClass(e.className),legacy:e.legacy,loadCallback:e.loadCallback}),this._init(e.identifier)))},_init:function(e){"string"==typeof e&&e.length?this._initElements(c.get(e),e):c.forEach(this._initElements.bind(this))},_initElements:function(e,t){for(var i=e.legacy?elBySelAll(e.elements):e.elements,n=0,r=i.length;n<r;n++){var o=i[n],c=a.identify(o);if(s.has(c))return;if(null!==o.closest(".popover"))return void s.set(c,{content:null,state:0});var d=e.legacy?c:~~o.getAttribute(e.attributeName);if(0!==d){o.addEventListener("mouseenter",_),o.addEventListener("mouseleave",b),"A"===o.nodeName&&elAttr(o,"href")&&o.addEventListener(WCF_CLICK_EVENT,g);var u=t+"-"+d;elData(o,"cache-id",u),l.set(c,{element:o,identifier:t,objectId:d}),s.has(u)||s.set(t+"-"+d,{content:null,state:0})}}},setContent:function(e,t,i){var n=e+"-"+t,r=s.get(n);if(void 0===r)throw new Error("Unable to find element for object id '"+t+"' (identifier: '"+e+"').");var c=a.createFragmentFromHtml(i);if(c.childElementCount||(c=a.createFragmentFromHtml("<p>"+i+"</p>")),r.content=c,r.state=2,o){var d=l.get(o).element;elData(d,"cache-id")===n&&this._show()}},_mouseEnter:function(e){if(!u){null!==h&&(window.clearTimeout(h),h=null);var t=a.identify(e.currentTarget);o===t&&null!==p&&(window.clearTimeout(p),p=null),d=t,h=window.setTimeout(function(){h=null,d===t&&this._show()}.bind(this),800)}},_mouseLeave:function(){d=null,null===p&&(null===v&&(v=this._hide.bind(this)),null!==p&&window.clearTimeout(p),p=window.setTimeout(v,500))},_popoverMouseEnter:function(){null!==p&&(window.clearTimeout(p),p=null)},_show:function(){null!==p&&(window.clearTimeout(p),p=null);var e=!1;f.classList.contains("active")?o!==d&&(this._hide(),e=!0):m.childElementCount&&(e=!0),e&&(f.classList.add("forceHide"),f.offsetTop,this._clearContent(),f.classList.remove("forceHide")),o=d;var t=l.get(o);if(void 0!==t){var i=s.get(elData(t.element,"cache-id"));2===i.state?(m.appendChild(i.content),this._rebuild(o)):0===i.state&&(i.state=1,c.get(t.identifier).loadCallback(t.objectId,this))}},_hide:function(){null!==p&&(window.clearTimeout(p),p=null),f.classList.remove("active")},_clearContent:function(){if(o&&m.childElementCount&&!f.classList.contains("active"))for(var e=s.get(elData(l.get(o).element,"cache-id"));m.childNodes.length;)e.content.appendChild(m.childNodes[0])},_rebuild:function(){f.classList.contains("active")||(f.classList.remove("forceHide"),f.classList.add("active"),r.set(f,l.get(o).element,{pointer:!0,vertical:"top"}))},_ajaxSetup:function(){return{silent:!0}},ajaxApi:function(t,i,n){if("function"!=typeof i)throw new TypeError("Expected a valid callback for parameter 'success'.");e.api(this,t,i,n)}}}),define("WoltLabSuite/Core/Ui/User/Ignore",["List","Dom/ChangeListener"],function(e,t){"use strict";var i=elByClass("ignoredUserMessage"),n=null,a=new e;return{init:function(){n=this._removeClass.bind(this),this._rebuild(),t.add("WoltLabSuite/Core/Ui/User/Ignore",this._rebuild.bind(this))},_rebuild:function(){for(var e,t=0,r=i.length;t<r;t++)e=i[t],a.has(e)||(e.addEventListener(WCF_CLICK_EVENT,n),a.add(e))},_removeClass:function(e){e.preventDefault();var t=e.currentTarget;t.classList.remove("ignoredUserMessage"),t.removeEventListener(WCF_CLICK_EVENT,n),a.delete(t),window.getSelection().removeAllRanges()}}}),define("WoltLabSuite/Core/Ui/Page/Header/Menu",["Environment","Language","Ui/Screen"],function(e,t,i){"use strict";var n,a,r,o,s=!1,l=0,c=[],d=[];return{init:function(){if(o=elBySel(".mainMenu .boxMenu"),null===(r=o&&o.childElementCount?o.children[0]:null))throw new Error("Unable to find the menu.");i.on("screen-lg",{enable:this._enable.bind(this),disable:this._disable.bind(this),setup:this._setup.bind(this)})},_enable:function(){s=!0,"safari"===e.browser()?window.setTimeout(this._rebuildVisibility.bind(this),1e3):(this._rebuildVisibility(),window.setTimeout(this._rebuildVisibility.bind(this),1e3))},_disable:function(){s=!1},_showNext:function(e){if(e.preventDefault(),d.length){var t=d.slice(0,3).pop();this._setMarginLeft(o.clientWidth-(t.offsetLeft+t.clientWidth)),o.lastElementChild===t&&n.classList.remove("active"),a.classList.add("active")}},_showPrevious:function(e){if(e.preventDefault(),c.length){var t=c.slice(-3)[0];this._setMarginLeft(-1*t.offsetLeft),o.firstElementChild===t&&a.classList.remove("active"),n.classList.add("active")}},_setMarginLeft:function(e){l=Math.min(l+e,0),r.style.setProperty("margin-left",l+"px","")},_rebuildVisibility:function(){if(s){c=[],d=[];var e=o.clientWidth;if(o.scrollWidth>e||l<0)for(var t,i=0,r=o.childElementCount;i<r;i++){t=o.children[i];var u=t.offsetLeft;u<0?c.push(t):u+t.clientWidth>e&&d.push(t)}a.classList[c.length?"add":"remove"]("active"),n.classList[d.length?"add":"remove"]("active")}},_setup:function(){this._setupOverflow(),this._setupA11y()},_setupOverflow:function(){n=elCreate("a"),n.className="mainMenuShowNext",n.href="#",n.innerHTML='<span class="icon icon32 fa-angle-right"></span>',n.addEventListener(WCF_CLICK_EVENT,this._showNext.bind(this)),o.parentNode.appendChild(n),a=elCreate("a"),a.className="mainMenuShowPrevious",a.href="#",a.innerHTML='<span class="icon icon32 fa-angle-left"></span>',a.addEventListener(WCF_CLICK_EVENT,this._showPrevious.bind(this)),o.parentNode.insertBefore(a,o.parentNode.firstChild);var e=this._rebuildVisibility.bind(this);r.addEventListener("transitionend",e),window.addEventListener("resize",function(){r.style.setProperty("margin-left","0px",""),l=0,e()}),this._enable()},_setupA11y:function(){elBySelAll(".boxMenuHasChildren",o,function(e){var i=!1,n=elBySel(".boxMenuLink",e);n&&(elAttr(n,"aria-haspopup",!0),elAttr(n,"aria-expanded",i));var a=elCreate("button");a.className="visuallyHidden",a.tabindex=0,elAttr(a,"role","button"),elAttr(a,"aria-label",t.get("wcf.global.button.showMenu")),e.insertBefore(a,n.nextSibling),a.addEventListener(WCF_CLICK_EVENT,function(){i=!i,elAttr(n,"aria-expanded",i),elAttr(a,"aria-label",i?t.get("wcf.global.button.hideMenu"):t.get("wcf.global.button.showMenu"))})}.bind(this))}}}),define("WoltLabSuite/Core/BootstrapFrontend",["WoltLabSuite/Core/BackgroundQueue","WoltLabSuite/Core/Bootstrap","WoltLabSuite/Core/Controller/Style/Changer","WoltLabSuite/Core/Controller/Popover","WoltLabSuite/Core/Ui/User/Ignore","WoltLabSuite/Core/Ui/Page/Header/Menu"],function(e,t,i,n,a,r){"use strict";return{setup:function(n){n.backgroundQueue.url=WSC_API_URL+n.backgroundQueue.url.substr(WCF_PATH.length),t.setup(),r.init(),n.styleChanger&&i.setup(),n.enableUserPopover&&this._initUserPopover(),e.setUrl(n.backgroundQueue.url),(Math.random()<.1||n.backgroundQueue.force)&&e.invoke(),a.init()},_initUserPopover:function(){n.init({attributeName:"data-user-id",className:"userLink",identifier:"com.woltlab.wcf.user",loadCallback:function(e,t){var i=function(i){t.setContent("com.woltlab.wcf.user",e,i.returnValues.template)};t.ajaxApi({actionName:"getUserProfile",className:"wcf\\data\\user\\UserProfileAction",objectIDs:[e]},i,i)}})}}}),define("WoltLabSuite/Core/Clipboard",[],function(){"use strict";return{copyTextToClipboard:function(e){if(navigator.clipboard)return navigator.clipboard.writeText(e);if(window.getSelection){var t=elCreate("textarea");t.contentEditable=!0,t.readOnly=!1,t.style.cssText="position: absolute; left: -9999px; top: -9999px; width: 0; height: 0;",document.body.appendChild(t);try{t.value=e;var i=document.createRange();i.selectNodeContents(t);var n=window.getSelection();return n.removeAllRanges(),n.addRange(i),t.setSelectionRange(0,999999),document.execCommand("copy")?Promise.resolve():Promise.reject(new Error("execCommand('copy') failed"))}finally{elRemove(t)}}return Promise.reject(new Error("Neither navigator.clipboard, nor window.getSelection is supported."))},copyElementTextToClipboard:function(e){return this.copyTextToClipboard(e.textContent)}}}),define("WoltLabSuite/Core/ColorUtil",[],function(){"use strict";var e={hsvToRgb:function(e,t,i){var n,a,r,o,s,l={r:0,g:0,b:0};if(n=Math.floor(e/60),a=e/60-n,t/=100,i/=100,r=i*(1-t),o=i*(1-t*a),s=i*(1-t*(1-a)),0==t)l.r=l.g=l.b=i;else switch(n){case 1:l.r=o,l.g=i,l.b=r;break;case 2:l.r=r,l.g=i,l.b=s;break;case 3:l.r=r,l.g=o,l.b=i;break;case 4:l.r=s,l.g=r,l.b=i;break;case 5:l.r=i,l.g=r,l.b=o;break;case 0:case 6:l.r=i,l.g=s,l.b=r}return{r:Math.round(255*l.r),g:Math.round(255*l.g),b:Math.round(255*l.b)}},rgbToHsv:function(e,t,i){var n,a,r,o,s,l;if(e/=255,t/=255,i/=255,o=Math.max(Math.max(e,t),i),s=Math.min(Math.min(e,t),i),l=o-s,n=0,o!==s){switch(o){case e:n=(t-i)/l*60;break;case t:n=60*(2+(i-e)/l);break;case i:n=60*(4+(e-t)/l)}n<0&&(n+=360)}return a=0===o?0:l/o,r=o,{h:Math.round(n),s:Math.round(100*a),v:Math.round(100*r)}},hexToRgb:function(e){if(/^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(e)){var t=e.split("");return"#"===t[0]&&t.shift(),3===t.length?{r:parseInt(t[0]+""+t[0],16),g:parseInt(t[1]+""+t[1],16),b:parseInt(t[2]+""+t[2],16)}:{r:parseInt(t[0]+""+t[1],16),g:parseInt(t[2]+""+t[3],16),b:parseInt(t[4]+""+t[5],16)}}return Number.NaN},rgbToHex:function(e,t,i){var n="0123456789ABCDEF";return void 0===t&&e.toString().match(/^rgba?\((\d+), ?(\d+), ?(\d+)(?:, ?[0-9.]+)?\)$/)&&(e=RegExp.$1,t=RegExp.$2,i=RegExp.$3),n.charAt((e-e%16)/16)+""+n.charAt(e%16)+n.charAt((t-t%16)/16)+n.charAt(t%16)+n.charAt((i-i%16)/16)+n.charAt(i%16)}};return window.__wcf_bc_colorUtil=e,e}),define("WoltLabSuite/Core/FileUtil",["Dictionary","StringUtil"],function(e,t){"use strict";var i=e.fromObject({zip:"archive",rar:"archive",tar:"archive",gz:"archive",mp3:"audio",ogg:"audio",wav:"audio",php:"code",html:"code",htm:"code",tpl:"code",js:"code",xls:"excel",ods:"excel",xlsx:"excel",gif:"image",jpg:"image",jpeg:"image",png:"image",bmp:"image",webp:"image",avi:"video",wmv:"video",mov:"video",mp4:"video",mpg:"video",mpeg:"video",flv:"video",pdf:"pdf",ppt:"powerpoint",pptx:"powerpoint",txt:"text",doc:"word",docx:"word",odt:"word"}),n=e.fromObject({"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/mpeg":"mp3","audio/mp3":"mp3","audio/ogg":"ogg","audio/x-wav":"wav","application/x-php":"php","text/html":"html","application/javascript":"js","application/vnd.ms-excel":"xls","application/vnd.oasis.opendocument.spreadsheet":"ods","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":"xlsx","image/gif":"gif","image/jpeg":"jpg","image/png":"png","image/x-ms-bmp":"bmp","image/bmp":"bmp","image/webp":"webp","video/x-msvideo":"avi","video/x-ms-wmv":"wmv","video/quicktime":"mov","video/mp4":"mp4","video/mpeg":"mpg","video/x-flv":"flv","application/pdf":"pdf","application/vnd.ms-powerpoint":"ppt","application/vnd.openxmlformats-officedocument.presentationml.presentation":"pptx","text/plain":"txt","application/msword":"doc","application/vnd.openxmlformats-officedocument.wordprocessingml.document":"docx","application/vnd.oasis.opendocument.text":"odt"});return{formatFilesize:function(e,i){void 0===i&&(i=2);var n="Byte";return e>=1e3&&(e/=1e3,n="kB"),e>=1e3&&(e/=1e3,n="MB"),e>=1e3&&(e/=1e3,n="GB"),e>=1e3&&(e/=1e3,n="TB"),t.formatNumeric(e,-i)+" "+n},getIconNameByFilename:function(e){var t=e.lastIndexOf(".");if(!1!==t){var n=e.substr(t+1);if(i.has(n))return i.get(n)}return""},getExtensionByMimeType:function(e){return n.has(e)?"."+n.get(e):""},blobToFile:function(e,t){var i=this.getExtensionByMimeType(e.type),n=window.File;try{new n([],"ie11-check")}catch(e){n=function(e,t,i){var n=Blob.call(this,e,i);return n.name=t,n.lastModifiedDate=new Date,n},n.prototype=Object.create(window.File.prototype)}return new n([e],t+i,{type:e.type})}}}),define("WoltLabSuite/Core/Permission",["Dictionary"],function(e){"use strict";var t=new e;return{add:function(e,i){if("boolean"!=typeof i)throw new TypeError("Permission value has to be boolean.");t.set(e,i)},addObject:function(e){for(var t in e)objOwns(e,t)&&this.add(t,e[t])},get:function(e){return!!t.has(e)&&t.get(e)}}});var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-([\w-]+)\b/i,t=0,i=_self.Prism={manual:_self.Prism&&_self.Prism.manual,disableWorkerMessageHandler:_self.Prism&&_self.Prism.disableWorkerMessageHandler,util:{encode:function(e){return e instanceof n?new n(e.type,i.util.encode(e.content),e.alias):"Array"===i.util.type(e)?e.map(i.util.encode):e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).match(/\[object (\w+)\]/)[1]},objId:function(e){return e.__id||Object.defineProperty(e,"__id",{value:++t}),e.__id},clone:function(e,t){var n=i.util.type(e);switch(t=t||{},n){case"Object":if(t[i.util.objId(e)])return t[i.util.objId(e)];var a={};t[i.util.objId(e)]=a;for(var r in e)e.hasOwnProperty(r)&&(a[r]=i.util.clone(e[r],t));return a;case"Array":if(t[i.util.objId(e)])return t[i.util.objId(e)];var a=[];return t[i.util.objId(e)]=a,e.forEach(function(e,n){a[n]=i.util.clone(e,t)}),a}return e}},languages:{extend:function(e,t){var n=i.util.clone(i.languages[e]);for(var a in t)n[a]=t[a];return n},insertBefore:function(e,t,n,a){a=a||i.languages;var r=a[e];if(2==arguments.length){n=arguments[1];for(var o in n)n.hasOwnProperty(o)&&(r[o]=n[o]);return r}var s={};for(var l in r)if(r.hasOwnProperty(l)){if(l==t)for(var o in n)n.hasOwnProperty(o)&&(s[o]=n[o]);s[l]=r[l]}var c=a[e];return a[e]=s,i.languages.DFS(i.languages,function(t,i){i===c&&t!=e&&(this[t]=s)}),s},DFS:function(e,t,n,a){a=a||{};for(var r in e)e.hasOwnProperty(r)&&(t.call(e,r,e[r],n||r),"Object"!==i.util.type(e[r])||a[i.util.objId(e[r])]?"Array"!==i.util.type(e[r])||a[i.util.objId(e[r])]||(a[i.util.objId(e[r])]=!0,i.languages.DFS(e[r],t,r,a)):(a[i.util.objId(e[r])]=!0,i.languages.DFS(e[r],t,null,a)))}},plugins:{},highlightAll:function(e,t){i.highlightAllUnder(document,e,t)},highlightAllUnder:function(e,t,n){var a={callback:n,selector:'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'};i.hooks.run("before-highlightall",a);for(var r,o=a.elements||e.querySelectorAll(a.selector),s=0;r=o[s++];)i.highlightElement(r,!0===t,a.callback)},highlightElement:function(t,n,a){for(var r,o,s=t;s&&!e.test(s.className);)s=s.parentNode;s&&(r=(s.className.match(e)||[,""])[1].toLowerCase(),o=i.languages[r]),t.className=t.className.replace(e,"").replace(/\s+/g," ")+" language-"+r,t.parentNode&&(s=t.parentNode,/pre/i.test(s.nodeName)&&(s.className=s.className.replace(e,"").replace(/\s+/g," ")+" language-"+r));var l=t.textContent,c={element:t,language:r,grammar:o,code:l};if(i.hooks.run("before-sanity-check",c),!c.code||!c.grammar)return c.code&&(i.hooks.run("before-highlight",c),c.element.textContent=c.code,i.hooks.run("after-highlight",c)),void i.hooks.run("complete",c);if(i.hooks.run("before-highlight",c),n&&_self.Worker){var d=new Worker(i.filename);d.onmessage=function(e){c.highlightedCode=e.data,i.hooks.run("before-insert",c),c.element.innerHTML=c.highlightedCode,a&&a.call(c.element),i.hooks.run("after-highlight",c),i.hooks.run("complete",c)},d.postMessage(JSON.stringify({language:c.language,code:c.code,immediateClose:!0}))}else c.highlightedCode=i.highlight(c.code,c.grammar,c.language),i.hooks.run("before-insert",c),c.element.innerHTML=c.highlightedCode,a&&a.call(t),i.hooks.run("after-highlight",c),i.hooks.run("complete",c)},highlight:function(e,t,a){var r={code:e,grammar:t,language:a};return i.hooks.run("before-tokenize",r),r.tokens=i.tokenize(r.code,r.grammar),i.hooks.run("after-tokenize",r),n.stringify(i.util.encode(r.tokens),r.language)},matchGrammar:function(e,t,n,a,r,o,s){var l=i.Token;for(var c in n)if(n.hasOwnProperty(c)&&n[c]){if(c==s)return;var d=n[c];d="Array"===i.util.type(d)?d:[d];for(var u=0;u<d.length;++u){var h=d[u],p=h.inside,f=!!h.lookbehind,m=!!h.greedy,g=0,v=h.alias;if(m&&!h.pattern.global){var _=h.pattern.toString().match(/[imuy]*$/)[0];h.pattern=RegExp(h.pattern.source,_+"g")}h=h.pattern||h;for(var b=a,w=r;b<t.length;w+=t[b].length,++b){var y=t[b];if(t.length>e.length)return;if(!(y instanceof l)){if(m&&b!=t.length-1){h.lastIndex=w;var C=h.exec(e);if(!C)break;for(var E=C.index+(f?C[1].length:0),L=C.index+C[0].length,I=b,S=w,A=t.length;I<A&&(S<L||!t[I].type&&!t[I-1].greedy);++I)S+=t[I].length,E>=S&&(++b,w=S);if(t[b]instanceof l)continue;D=I-b,y=e.slice(w,S),C.index-=w}else{h.lastIndex=0;var C=h.exec(y),D=1}if(C){f&&(g=C[1]?C[1].length:0);var E=C.index+g,C=C[0].slice(g),L=E+C.length,x=y.slice(0,E),T=y.slice(L),B=[b,D];x&&(++b,w+=x.length,B.push(x));var k=new l(c,p?i.tokenize(C,p):C,v,C,m);if(B.push(k),T&&B.push(T),Array.prototype.splice.apply(t,B),1!=D&&i.matchGrammar(e,t,n,b,w,!0,c),o)break}else if(o)break}}}}},tokenize:function(e,t,n){var a=[e],r=t.rest;if(r){for(var o in r)t[o]=r[o];delete t.rest}return i.matchGrammar(e,a,t,0,0,!1),a},hooks:{all:{},add:function(e,t){var n=i.hooks.all;n[e]=n[e]||[],n[e].push(t)},run:function(e,t){var n=i.hooks.all[e];if(n&&n.length)for(var a,r=0;a=n[r++];)a(t)}}},n=i.Token=function(e,t,i,n,a){this.type=e,this.content=t,this.alias=i,this.length=0|(n||"").length,this.greedy=!!a};if(n.stringify=function(e,t,a){if("string"==typeof e)return e;if("Array"===i.util.type(e))return e.map(function(i){return n.stringify(i,t,e)}).join("");var r={type:e.type,content:n.stringify(e.content,t,a),
+tag:"span",classes:["token",e.type],attributes:{},language:t,parent:a};if(e.alias){var o="Array"===i.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(r.classes,o)}i.hooks.run("wrap",r);var s=Object.keys(r.attributes).map(function(e){return e+'="'+(r.attributes[e]||"").replace(/"/g,"&quot;")+'"'}).join(" ");return"<"+r.tag+' class="'+r.classes.join(" ")+'"'+(s?" "+s:"")+">"+r.content+"</"+r.tag+">"},!_self.document)return _self.addEventListener?(i.disableWorkerMessageHandler||_self.addEventListener("message",function(e){var t=JSON.parse(e.data),n=t.language,a=t.code,r=t.immediateClose;_self.postMessage(i.highlight(a,i.languages[n],n)),r&&_self.close()},!1),_self.Prism):_self.Prism;var a=document.currentScript||[].slice.call(document.getElementsByTagName("script")).pop();return a&&(i.filename=a.src,i.manual||a.hasAttribute("data-manual")||("loading"!==document.readyState?window.requestAnimationFrame?window.requestAnimationFrame(i.highlightAll):window.setTimeout(i.highlightAll,16):document.addEventListener("DOMContentLoaded",i.highlightAll))),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism),define("prism/prism",function(){}),window.Prism=window.Prism||{},window.Prism.manual=!0,define("WoltLabSuite/Core/Prism",["prism/prism"],function(){return Prism.wscSplitIntoLines=function(e){function t(){var e=elCreate("span");return elData(e,"number",o++),r.appendChild(e),e}var i,n,a,r=document.createDocumentFragment(),o=1;for(i=document.createNodeIterator(e,NodeFilter.SHOW_TEXT,function(){return NodeFilter.FILTER_ACCEPT},!1),a=t();n=i.nextNode();)n.data.split(/\r?\n/).forEach(function(i,r){var o,s;for(r>=1&&(a.appendChild(document.createTextNode("\n")),a=t()),o=document.createTextNode(i),s=n.parentNode;s!==e;){var l=s.cloneNode(!1);l.appendChild(o),o=l,s=s.parentNode}a.appendChild(o)});return r},Prism}),define("WoltLabSuite/Core/Upload",["AjaxRequest","Core","Dom/ChangeListener","Language","Dom/Util","Dom/Traverse"],function(e,t,i,n,a,r){"use strict";function o(e,i,n){if(n=n||{},void 0===n.className)throw new Error("Missing class name.");if(this._options=t.extend({action:"upload",multiple:!1,name:"__files[]",singleFileRequests:!1,url:"index.php?ajax-upload/&t="+SECURITY_TOKEN},n),this._options.url=t.convertLegacyUrl(this._options.url),0===this._options.url.indexOf("index.php")&&(this._options.url=WSC_API_URL+this._options.url),this._buttonContainer=elById(e),null===this._buttonContainer)throw new Error("Element id '"+e+"' is unknown.");if(this._target=elById(i),null===i)throw new Error("Element id '"+i+"' is unknown.");if(n.multiple&&"UL"!==this._target.nodeName&&"OL"!==this._target.nodeName&&"TBODY"!==this._target.nodeName)throw new Error("Target element has to be list or table body if uploading multiple files is supported.");this._fileElements=[],this._internalFileId=0,this._multiFileUploadIds=[],this._createButton()}return o.prototype={_createButton:function(){this._fileUpload=elCreate("input"),elAttr(this._fileUpload,"type","file"),elAttr(this._fileUpload,"name",this._options.name),this._options.multiple&&elAttr(this._fileUpload,"multiple","true"),this._fileUpload.addEventListener("change",this._upload.bind(this)),this._button=elCreate("p"),this._button.className="button uploadButton",elAttr(this._button,"role","button"),elAttr(this._button,"tabindex","0");var e=elCreate("span");e.textContent=n.get("wcf.global.button.upload"),this._button.appendChild(e),a.prepend(this._fileUpload,this._button),this._insertButton(),i.trigger()},_createFileElement:function(e){var t=elCreate("progress");if(elAttr(t,"max",100),"OL"===this._target.nodeName||"UL"===this._target.nodeName){var i=elCreate("li");return i.innerText=e.name,i.appendChild(t),this._target.appendChild(i),i}if("TBODY"===this._target.nodeName)return this._createFileTableRow(e);var n=elCreate("p");return n.appendChild(t),this._target.appendChild(n),n},_createFileElements:function(e){if(e.length){var t=this._fileElements.length;this._fileElements[t]=[];for(var n=0,a=e.length;n<a;n++){var r=e[n],o=this._createFileElement(r);o.classList.contains("uploadFailed")||(elData(o,"filename",r.name),elData(o,"internal-file-id",this._internalFileId++),this._fileElements[t][n]=o)}return i.trigger(),t}return null},_createFileTableRow:function(e){throw new Error("Has to be implemented in subclass.")},_failure:function(e,t,i,n,a){return!0},_getParameters:function(){return{}},_getFormData:function(){return{}},_insertButton:function(){a.prepend(this._button,this._buttonContainer)},_progress:function(e,t){var i=Math.round(t.loaded/t.total*100);for(var n in this._fileElements[e]){var a=elByTag("PROGRESS",this._fileElements[e][n]);1===a.length&&elAttr(a[0],"value",i)}},_removeButton:function(){elRemove(this._button),i.trigger()},_success:function(e,t,i,n,a){},_upload:function(e,t,i){for(var n=r.childrenByClass(this._target,"uploadFailed"),a=0,o=n.length;a<o;a++)elRemove(n[a]);var s=null,l=[];if(t)l.push(t);else if(i){var c="";switch(i.type){case"image/jpeg":c=".jpg";break;case"image/gif":c=".gif";break;case"image/png":c=".png"}l.push({name:"pasted-from-clipboard"+c})}else l=this._fileUpload.files;if(l.length&&this.validateUpload(l))if(this._options.singleFileRequests){s=[];for(var a=0,o=l.length;a<o;a++){var d=this._uploadFiles([l[a]],i);1!==l.length&&this._multiFileUploadIds.push(d),s.push(d)}}else s=this._uploadFiles(l,i);return this._removeButton(),this._createButton(),s},validateUpload:function(e){return!0},_uploadFiles:function(t,i){var n=this._createFileElements(t);if(!this._fileElements[n].length)return null;for(var a=new FormData,r=0,o=t.length;r<o;r++)if(this._fileElements[n][r]){var s=elData(this._fileElements[n][r],"internal-file-id");i?a.append("__files["+s+"]",i,t[r].name):a.append("__files["+s+"]",t[r])}a.append("actionName",this._options.action),a.append("className",this._options.className),"upload"===this._options.action&&a.append("interfaceName","wcf\\data\\IUploadAction");var l=function(e,t){t=t||"";for(var i in e)if("object"==typeof e[i]){var n=0===t.length?i:t+"["+i+"]";l(e[i],n)}else{var r=0===t.length?i:t+"["+i+"]";a.append(r,e[i])}};return l(this._getParameters(),"parameters"),l(this._getFormData()),new e({data:a,contentType:!1,failure:this._failure.bind(this,n),silent:!0,success:this._success.bind(this,n),uploadProgress:this._progress.bind(this,n),url:this._options.url,withCredentials:!0}).sendRequest(),n},hasPendingUploads:function(){for(var e in this._fileElements)for(var t in this._fileElements[e]){var i=elByTag("PROGRESS",this._fileElements[e][t]);if(1===i.length)return!0}return!1},uploadBlob:function(e){return this._upload(null,null,e)},uploadFile:function(e){return this._upload(null,e)}},o}),define("WoltLabSuite/Core/User",[],function(){"use strict";var e,t=!1;return{getLink:function(){return e},init:function(i,n,a){if(t)throw new Error("User has already been initialized.");Object.defineProperty(this,"userId",{value:i,writable:!1}),Object.defineProperty(this,"username",{value:n,writable:!1}),e=a,t=!0}}}),define("WoltLabSuite/Core/Ajax/Jsonp",["Core"],function(e){"use strict";return{send:function(t,i,n,a){if(t="string"==typeof t?t.trim():"",0===t.length)throw new Error("Expected a non-empty string for parameter 'url'.");if("function"!=typeof i)throw new TypeError("Expected a valid callback function for parameter 'success'.");a=e.extend({parameterName:"callback",timeout:10},a||{});var r,o="wcf_jsonp_"+e.getUuid().replace(/-/g,"").substr(0,8),s=window.setTimeout(function(){"function"==typeof n&&n(),window[o]=void 0,elRemove(r)},1e3*(~~a.timeout||10));window[o]=function(){window.clearTimeout(s),i.apply(null,arguments),window[o]=void 0,elRemove(r)},t+=-1===t.indexOf("?")?"?":"&",t+=a.parameterName+"="+o,r=elCreate("script"),r.async=!0,elAttr(r,"src",t),document.head.appendChild(r)}}}),define("WoltLabSuite/Core/Ui/Notification",["Language"],function(e){"use strict";var t=!1,i=null,n=null,a=null,r=null,o=null;return{show:function(s,l,c){t||(this._init(),i="function"==typeof l?l:null,n.className=c||"success",n.textContent=e.get(s||"wcf.global.success"),t=!0,a.classList.add("active"),r=setTimeout(o,2e3))},_init:function(){null===a&&(o=this._hide.bind(this),a=elCreate("div"),a.id="systemNotification",n=elCreate("p"),n.addEventListener(WCF_CLICK_EVENT,o),a.appendChild(n),document.body.appendChild(a))},_hide:function(){clearTimeout(r),a.classList.remove("active"),null!==i&&i(),t=!1}}}),define("prism/prism-meta",[],function(){return{markup:{title:"Markup",file:"markup"},html:{title:"HTML",file:"markup"},xml:{title:"XML",file:"markup"},svg:{title:"SVG",file:"markup"},mathml:{title:"MathML",file:"markup"},css:{title:"CSS",file:"css"},clike:{title:"C-like",file:"clike"},javascript:{title:"JavaScript",file:"javascript"},abap:{title:"ABAP",file:"abap"},actionscript:{title:"ActionScript",file:"actionscript"},ada:{title:"Ada",file:"ada"},apacheconf:{title:"Apache Configuration",file:"apacheconf"},apl:{title:"APL",file:"apl"},applescript:{title:"AppleScript",file:"applescript"},arduino:{title:"Arduino",file:"arduino"},arff:{title:"ARFF",file:"arff"},asciidoc:{title:"AsciiDoc",file:"asciidoc"},asm6502:{title:"6502 Assembly",file:"asm6502"},aspnet:{title:"ASP.NET (C#)",file:"aspnet"},autohotkey:{title:"AutoHotkey",file:"autohotkey"},autoit:{title:"AutoIt",file:"autoit"},bash:{title:"Bash",file:"bash"},basic:{title:"BASIC",file:"basic"},batch:{title:"Batch",file:"batch"},bison:{title:"Bison",file:"bison"},brainfuck:{title:"Brainfuck",file:"brainfuck"},bro:{title:"Bro",file:"bro"},c:{title:"C",file:"c"},csharp:{title:"C#",file:"csharp"},cpp:{title:"C++",file:"cpp"},coffeescript:{title:"CoffeeScript",file:"coffeescript"},clojure:{title:"Clojure",file:"clojure"},crystal:{title:"Crystal",file:"crystal"},csp:{title:"Content-Security-Policy",file:"csp"},"css-extras":{title:"CSS Extras",file:"css-extras"},d:{title:"D",file:"d"},dart:{title:"Dart",file:"dart"},diff:{title:"Diff",file:"diff"},django:{title:"Django/Jinja2",file:"django"},docker:{title:"Docker",file:"docker"},eiffel:{title:"Eiffel",file:"eiffel"},elixir:{title:"Elixir",file:"elixir"},elm:{title:"Elm",file:"elm"},erb:{title:"ERB",file:"erb"},erlang:{title:"Erlang",file:"erlang"},fsharp:{title:"F#",file:"fsharp"},flow:{title:"Flow",file:"flow"},fortran:{title:"Fortran",file:"fortran"},gedcom:{title:"GEDCOM",file:"gedcom"},gherkin:{title:"Gherkin",file:"gherkin"},git:{title:"Git",file:"git"},glsl:{title:"GLSL",file:"glsl"},gml:{title:"GameMaker Language",file:"gml"},go:{title:"Go",file:"go"},graphql:{title:"GraphQL",file:"graphql"},groovy:{title:"Groovy",file:"groovy"},haml:{title:"Haml",file:"haml"},handlebars:{title:"Handlebars",file:"handlebars"},haskell:{title:"Haskell",file:"haskell"},haxe:{title:"Haxe",file:"haxe"},http:{title:"HTTP",file:"http"},hpkp:{title:"HTTP Public-Key-Pins",file:"hpkp"},hsts:{title:"HTTP Strict-Transport-Security",file:"hsts"},ichigojam:{title:"IchigoJam",file:"ichigojam"},icon:{title:"Icon",file:"icon"},inform7:{title:"Inform 7",file:"inform7"},ini:{title:"Ini",file:"ini"},io:{title:"Io",file:"io"},j:{title:"J",file:"j"},java:{title:"Java",file:"java"},jolie:{title:"Jolie",file:"jolie"},json:{title:"JSON",file:"json"},julia:{title:"Julia",file:"julia"},keyman:{title:"Keyman",file:"keyman"},kotlin:{title:"Kotlin",file:"kotlin"},latex:{title:"LaTeX",file:"latex"},less:{title:"Less",file:"less"},liquid:{title:"Liquid",file:"liquid"},lisp:{title:"Lisp",file:"lisp"},livescript:{title:"LiveScript",file:"livescript"},lolcode:{title:"LOLCODE",file:"lolcode"},lua:{title:"Lua",file:"lua"},makefile:{title:"Makefile",file:"makefile"},markdown:{title:"Markdown",file:"markdown"},"markup-templating":{title:"Markup templating",file:"markup-templating"},matlab:{title:"MATLAB",file:"matlab"},mel:{title:"MEL",file:"mel"},mizar:{title:"Mizar",file:"mizar"},monkey:{title:"Monkey",file:"monkey"},n4js:{title:"N4JS",file:"n4js"},nasm:{title:"NASM",file:"nasm"},nginx:{title:"nginx",file:"nginx"},nim:{title:"Nim",file:"nim"},nix:{title:"Nix",file:"nix"},nsis:{title:"NSIS",file:"nsis"},objectivec:{title:"Objective-C",file:"objectivec"},ocaml:{title:"OCaml",file:"ocaml"},opencl:{title:"OpenCL",file:"opencl"},oz:{title:"Oz",file:"oz"},parigp:{title:"PARI/GP",file:"parigp"},parser:{title:"Parser",file:"parser"},pascal:{title:"Pascal",file:"pascal"},perl:{title:"Perl",file:"perl"},php:{title:"PHP",file:"php"},"php-extras":{title:"PHP Extras",file:"php-extras"},plsql:{title:"PL/SQL",file:"plsql"},powershell:{title:"PowerShell",file:"powershell"},processing:{title:"Processing",file:"processing"},prolog:{title:"Prolog",file:"prolog"},properties:{title:".properties",file:"properties"},protobuf:{title:"Protocol Buffers",file:"protobuf"},pug:{title:"Pug",file:"pug"},puppet:{title:"Puppet",file:"puppet"},pure:{title:"Pure",file:"pure"},python:{title:"Python",file:"python"},q:{title:"Q (kdb+ database)",file:"q"},qore:{title:"Qore",file:"qore"},r:{title:"R",file:"r"},jsx:{title:"React JSX",file:"jsx"},tsx:{title:"React TSX",file:"tsx"},renpy:{title:"Ren'py",file:"renpy"},reason:{title:"Reason",file:"reason"},rest:{title:"reST (reStructuredText)",file:"rest"},rip:{title:"Rip",file:"rip"},roboconf:{title:"Roboconf",file:"roboconf"},ruby:{title:"Ruby",file:"ruby"},rust:{title:"Rust",file:"rust"},sas:{title:"SAS",file:"sas"},sass:{title:"Sass (Sass)",file:"sass"},scss:{title:"Sass (Scss)",file:"scss"},scala:{title:"Scala",file:"scala"},scheme:{title:"Scheme",file:"scheme"},smalltalk:{title:"Smalltalk",file:"smalltalk"},smarty:{title:"Smarty",file:"smarty"},sql:{title:"SQL",file:"sql"},soy:{title:"Soy (Closure Template)",file:"soy"},stylus:{title:"Stylus",file:"stylus"},swift:{title:"Swift",file:"swift"},tap:{title:"TAP",file:"tap"},tcl:{title:"Tcl",file:"tcl"},textile:{title:"Textile",file:"textile"},tt2:{title:"Template Toolkit 2",file:"tt2"},twig:{title:"Twig",file:"twig"},typescript:{title:"TypeScript",file:"typescript"},vbnet:{title:"VB.Net",file:"vbnet"},velocity:{title:"Velocity",file:"velocity"},verilog:{title:"Verilog",file:"verilog"},vhdl:{title:"VHDL",file:"vhdl"},vim:{title:"vim",file:"vim"},"visual-basic":{title:"Visual Basic",file:"visual-basic"},wasm:{title:"WebAssembly",file:"wasm"},wiki:{title:"Wiki markup",file:"wiki"},xeora:{title:"Xeora",file:"xeora"},xojo:{title:"Xojo (REALbasic)",file:"xojo"},xquery:{title:"XQuery",file:"xquery"},yaml:{title:"YAML",file:"yaml"}}}),define("WoltLabSuite/Core/Bbcode/Code",["Language","WoltLabSuite/Core/Ui/Notification","WoltLabSuite/Core/Clipboard","WoltLabSuite/Core/Prism","prism/prism-meta"],function(e,t,i,n,a){"use strict";function r(e){var t;this.container=e,this.codeContainer=elBySel(".codeBoxCode > code",this.container),this.language=null;for(var i=0;i<this.codeContainer.classList.length;i++)(t=this.codeContainer.classList[i].match(/language-(.*)/))&&(this.language=t[1])}var o=function(e){return function(){var t=arguments;return new Promise(function(i,n){var a=function(){try{i(e.apply(null,t))}catch(e){n(e)}};window.requestIdleCallback?window.requestIdleCallback(a,{timeout:5e3}):setTimeout(a,0)})}};return r.processAll=function(){elBySelAll(".codeBox:not([data-processed])",document,function(e){elData(e,"processed","1");var t=new r(e);t.language&&t.highlight(),t.createCopyButton()})},r.prototype={createCopyButton:function(){var n=elBySel(".codeBoxHeader",this.container),a=elCreate("span");a.className="icon icon24 fa-files-o pointer jsTooltip",a.setAttribute("title",e.get("wcf.message.bbcode.code.copy")),a.addEventListener("click",function(){i.copyElementTextToClipboard(this.codeContainer).then(function(){t.show(e.get("wcf.message.bbcode.code.copy.success"))})}.bind(this)),n.appendChild(a)},highlight:function(){return this.language?a[this.language]?(this.container.classList.add("highlighting"),require(["prism/components/prism-"+a[this.language].file]).then(o(function(){var e=n.languages[this.language];if(!e)throw new Error("Invalid language "+language+" given.");var t=elCreate("div");return t.innerHTML=n.highlight(this.codeContainer.textContent,e,this.language),t}.bind(this))).then(o(function(e){var t=n.wscSplitIntoLines(e),i=elBySelAll("[data-number]",t),a=elBySelAll(".codeBoxLine > span",this.codeContainer);if(i.length!==a.length)throw new Error("Unreachable");for(var r=[],s=0,l=i.length;s<l;s+=50)r.push(o(function(e){for(var t=Math.min(e+50,l),n=e;n<t;n++)a[n].parentNode.replaceChild(i[n],a[n])})(s));return Promise.all(r)}.bind(this))).then(function(){this.container.classList.remove("highlighting"),this.container.classList.add("highlighted")}.bind(this))):Promise.reject(new Error("Unknown language "+this.language)):Promise.reject(new Error("No language detected"))}},r}),define("WoltLabSuite/Core/Bbcode/Collapsible",[],function(){"use strict";var e=elByClass("jsCollapsibleBbcode");return{observe:function(){for(var t,i;e.length;)t=e[0],i=null,elBySelAll(".toggleButton:not(.jsToggleButtonEnabled)",t,function(e){e.closest(".jsCollapsibleBbcode")===t&&(i=e)}),i&&function(e,t){var i=function(i){if(e.classList.toggle("collapsed")){if(t.textContent=elData(t,"title-expand"),i instanceof Event){var n=e.getBoundingClientRect().top;if(n<0){var a=window.pageYOffset+(n-100);a<0&&(a=0),window.scrollTo(window.pageXOffset,a)}}}else t.textContent=elData(t,"title-collapse")};t.classList.add("jsToggleButtonEnabled"),t.addEventListener(WCF_CLICK_EVENT,i),0!==e.scrollTop&&i(),e.addEventListener("scroll",function(){e.classList.contains("collapsed")&&i()})}(t,i),t.classList.remove("jsCollapsibleBbcode")}}}),define("WoltLabSuite/Core/Controller/Captcha",["Dictionary"],function(e){"use strict";var t=new e;return{add:function(e,i){if(t.has(e))throw new Error("Captcha with id '"+e+"' is already registered.");if("function"!=typeof i)throw new TypeError("Expected a valid callback for parameter 'callback'.");t.set(e,i)},delete:function(e){if(!t.has(e))throw new Error("Unknown captcha with id '"+e+"'.");t.delete(e)},has:function(e){return t.has(e)},getData:function(e){if(!t.has(e))throw new Error("Unknown captcha with id '"+e+"'.");return t.get(e)()}}}),define("WoltLabSuite/Core/Controller/Clipboard",["Ajax","Core","Dictionary","EventHandler","Language","List","ObjectMap","Dom/ChangeListener","Dom/Traverse","Dom/Util","Ui/Confirmation","Ui/SimpleDropdown","WoltLabSuite/Core/Ui/Page/Action","Ui/Screen"],function(e,t,i,n,a,r,o,s,l,c,d,u,h,p){"use strict";var f=new i,m=new i,g=new i,v=elByClass("jsClipboardContainer"),_=new o,b=new r,w={},y=new i,C=null,E=null,L=null,I='.messageCheckboxLabel > input[type="checkbox"], .message .messageClipboardCheckbox > input[type="checkbox"], .messageGroupList .columnMark > label > input[type="checkbox"]';return{setup:function(e){if(!e.pageClassName)throw new Error("Expected a non-empty string for parameter 'pageClassName'.");if(null===C)C=this._mark.bind(this),E=this._executeAction.bind(this),L=this._unmarkAll.bind(this),w=t.extend({hasMarkedItems:!1,pageClassNames:[e.pageClassName],pageObjectId:0},e),delete w.pageClassName;else{if(e.pageObjectId)throw new Error("Cannot load secondary clipboard with page object id set.");w.pageClassNames.push(e.pageClassName)}Element.prototype.matches||(Element.prototype.matches=Element.prototype.msMatchesSelector),this._initContainers(),w.hasMarkedItems&&v.length&&this._loadMarkedItems(),s.add("WoltLabSuite/Core/Controller/Clipboard",this._initContainers.bind(this))},reload:function(){f.size&&this._loadMarkedItems()},_initContainers:function(){for(var e=0,t=v.length;e<t;e++){var i=v[e],n=c.identify(i),o=f.get(n);if(void 0===o){var s=elBySel(".jsClipboardMarkAll",i);if(null!==s){if(s.matches(I)){var l=s.closest("label");elAttr(l,"role","checkbox"),elAttr(l,"tabindex","0"),elAttr(l,"aria-checked",!1),elAttr(l,"aria-label",a.get("wcf.clipboard.item.markAll")),l.addEventListener("keyup",function(e){13!==e.keyCode&&32!==e.keyCode||h.click()})}elData(s,"container-id",n),s.addEventListener(WCF_CLICK_EVENT,this._markAll.bind(this))}o={checkboxes:elByClass("jsClipboardItem",i),element:i,markAll:s,markedObjectIds:new r},f.set(n,o)}for(var d=0,u=o.checkboxes.length;d<u;d++){var h=o.checkboxes[d];b.has(h)||(elData(h,"container-id",n),function(e){if(e.matches(I)){var t=e.closest("label");elAttr(t,"role","checkbox"),elAttr(t,"tabindex","0"),elAttr(t,"aria-checked",!1),elAttr(t,"aria-label",a.get("wcf.clipboard.item.mark")),t.addEventListener("keyup",function(t){13!==t.keyCode&&32!==t.keyCode||e.click()})}null===e.closest("a")?e.addEventListener(WCF_CLICK_EVENT,C):e.addEventListener(WCF_CLICK_EVENT,function(t){t.preventDefault(),window.setTimeout(function(){e.checked=!e.checked,C(null,e)},10)})}(h),b.add(h))}}},_loadMarkedItems:function(){e.api(this,{actionName:"getMarkedItems",parameters:{pageClassNames:w.pageClassNames,pageObjectID:w.pageObjectId}})},_markAll:function(e){var t=e.currentTarget,i="INPUT"!==t.nodeName||t.checked;"checkbox"===elAttr(t.parentNode,"role")&&elAttr(t.parentNode,"aria-checked",i);for(var n=[],a=elData(t,"container-id"),r=f.get(a),o=elData(r.element,"type"),s=0,c=r.checkboxes.length;s<c;s++){var d=r.checkboxes[s],u=~~elData(d,"object-id");i?d.checked||(d.checked=!0,r.markedObjectIds.add(u),n.push(u)):d.checked&&(d.checked=!1,r.markedObjectIds.delete(u),n.push(u)),"checkbox"===elAttr(d.parentNode,"role")&&elAttr(d.parentNode,"aria-checked",i);var h=l.parentByClass(t,"jsClipboardObject");null!==h&&h.classList[i?"addClass":"removeClass"]("jsMarked")}this._saveState(o,n,i)},_mark:function(e,t){t=e instanceof Event?e.currentTarget:t;var i=~~elData(t,"object-id"),n=t.checked,a=elData(t,"container-id"),r=f.get(a),o=elData(r.element,"type"),s=l.parentByClass(t,"jsClipboardObject");if(r.markedObjectIds[n?"add":"delete"](i),s.classList[n?"add":"remove"]("jsMarked"),null!==r.markAll){for(var c=!0,d=0,u=r.checkboxes.length;d<u;d++)if(!r.checkboxes[d].checked){c=!1;break}r.markAll.checked=c,"checkbox"===elAttr(r.markAll.parentNode,"role")&&elAttr(r.markAll.parentNode,"aria-checked",n)}"checkbox"===elAttr(t.parentNode,"role")&&elAttr(t.parentNode,"aria-checked",t.checked),this._saveState(o,[i],n)},_saveState:function(t,i,n){e.api(this,{actionName:n?"mark":"unmark",parameters:{pageClassNames:w.pageClassNames,pageObjectID:w.pageObjectId,objectIDs:i,objectType:t}})},_executeAction:function(e){var t=e.currentTarget,i=_.get(t);if(i.url)return void(window.location.href=i.url);var a=function(){var e=elData(t,"type");n.fire("com.woltlab.wcf.clipboard",e,{data:i,listItem:t,responseData:null})},r="string"==typeof i.internalData.confirmMessage?i.internalData.confirmMessage:"",o=!0;if("object"==typeof i.parameters&&i.parameters.actionName&&i.parameters.className){if("unmarkAll"===i.parameters.actionName||Array.isArray(i.parameters.objectIDs))if(r.length){var s="string"==typeof i.internalData.template?i.internalData.template:"";d.show({confirm:function(){var e={};if(s.length)for(var n=elBySelAll("input, select, textarea",d.getContentElement()),a=0,r=n.length;a<r;a++){var o=n[a],l=elAttr(o,"name");switch(o.nodeName){case"INPUT":("checkbox"!==o.type&&"radio"!==o.type||o.checked)&&(e[l]=elAttr(o,"value"));break;case"SELECT":e[l]=o.value;break;case"TEXTAREA":e[l]=o.value.trim()}}this._executeProxyAction(t,i,e)}.bind(this),message:r,template:s})}else this._executeProxyAction(t,i)}else r.length&&(o=!1,d.show({confirm:a,message:r}));o&&a()},_executeProxyAction:function(t,i,a){a=a||{};var r="unmarkAll"!==i.parameters.actionName?i.parameters.objectIDs:[],o={data:a};if("object"==typeof i.internalData.parameters)for(var s in i.internalData.parameters)i.internalData.parameters.hasOwnProperty(s)&&(o[s]=i.internalData.parameters[s]);e.api(this,{actionName:i.parameters.actionName,className:i.parameters.className,objectIDs:r,parameters:o},function(e){if("unmarkAll"!==i.actionName){var a=elData(t,"type");if(n.fire("com.woltlab.wcf.clipboard",a,{data:i,listItem:t,responseData:e}),y.has(a)&&-1!==y.get(a).indexOf(e.actionName))return void window.location.reload()}this._loadMarkedItems()}.bind(this))},_unmarkAll:function(t){var i=elData(t.currentTarget,"type");e.api(this,{actionName:"unmarkAll",parameters:{objectType:i}})},_ajaxSetup:function(){return{data:{className:"wcf\\data\\clipboard\\item\\ClipboardItemAction"}}},_ajaxSuccess:function(e){if("unmarkAll"===e.actionName)return void f.forEach(function(t){if(elData(t.element,"type")===e.returnValues.objectType){for(var i=elByClass("jsMarked",t.element);i.length;)i[0].classList.remove("jsMarked");null!==t.markAll&&(t.markAll.checked=!1,"checkbox"===elAttr(t.markAll.parentNode,"role")&&elAttr(t.markAll.parentNode,"aria-checked",!1));for(var n=0,a=t.checkboxes.length;n<a;n++)t.checkboxes[n].checked=!1,"checkbox"===elAttr(t.checkboxes[n].parentNode,"role")&&elAttr(t.checkboxes[n].parentNode,"aria-checked",!1);h.remove("wcfClipboard-"+e.returnValues.objectType)}}.bind(this));_=new o,y=new i,f.forEach(function(t){var i=elData(t.element,"type"),n=e.returnValues.markedItems&&e.returnValues.markedItems.hasOwnProperty(i)?e.returnValues.markedItems[i]:[];this._rebuildMarkings(t,n)}.bind(this));var t,n=[];if(e.returnValues&&e.returnValues.items)for(t in e.returnValues.items)e.returnValues.items.hasOwnProperty(t)&&n.push(t);if(m.forEach(function(e,t){-1===n.indexOf(t)&&(h.remove("wcfClipboard-"+t),g.get(t).innerHTML="")}),e.returnValues&&e.returnValues.items){var r,s,l,c,d,p,v,b,w,C,I;for(t in e.returnValues.items)if(e.returnValues.items.hasOwnProperty(t)){d=e.returnValues.items[t],y.set(t,d.reloadPageOnSuccess),s=!1,c=m.get(t),l=g.get(t),void 0===c?(s=!0,c=elCreate("a"),c.className="dropdownToggle",c.textContent=d.label,m.set(t,c),l=elCreate("ol"),l.className="dropdownMenu",g.set(t,l)):(c.textContent=d.label,l.innerHTML="");for(w in d.items)d.items.hasOwnProperty(w)&&(b=d.items[w],v=elCreate("li"),C=elCreate("span"),C.textContent=b.label,v.appendChild(C),l.appendChild(v),elData(v,"type",t),v.addEventListener(WCF_CLICK_EVENT,E),_.set(v,b));p=elCreate("li"),p.classList.add("dropdownDivider"),l.appendChild(p),I=elCreate("li"),elData(I,"type",t),C=elCreate("span"),C.textContent=a.get("wcf.clipboard.item.unmarkAll"),I.appendChild(C),I.addEventListener(WCF_CLICK_EVENT,L),l.appendChild(I),-1!==n.indexOf(t)&&(r="wcfClipboard-"+t,h.has(r)?h.show(r):h.add(r,c)),s&&(c.parentNode.classList.add("dropdown"),c.parentNode.appendChild(l),u.init(c))}}},_rebuildMarkings:function(e,t){for(var i=!0,n=0,a=e.checkboxes.length;n<a;n++){var r=e.checkboxes[n],o=l.parentByClass(r,"jsClipboardObject"),s=-1!==t.indexOf(~~elData(r,"object-id"));s||(i=!1),r.checked=s,o.classList[s?"add":"remove"]("jsMarked"),"checkbox"===elAttr(r.parentNode,"role")&&elAttr(r.parentNode,"aria-checked",s)}if(null!==e.markAll){e.markAll.checked=i,"checkbox"===elAttr(e.markAll.parentNode,"role")&&elAttr(e.markAll.parentNode,"aria-checked",i);for(var c=e.markAll;c=c.parentNode;)if(c instanceof Element&&c.classList.contains("columnMark")){c=c.parentNode;break}c&&c.classList[i?"add":"remove"]("jsMarked")}},hideEditor:function(e){h.remove("wcfClipboard-"+e),p.pageOverlayOpen()},showEditor:function(){this._loadMarkedItems(),p.pageOverlayClose()},unmark:function(e,t){this._saveState(e,t,!1)}}}),define("WoltLabSuite/Core/Image/ExifUtil",[],function(){"use strict";var e={SOI:216,APP0:224,APP1:225,APP2:226,APP3:227,APP4:228,APP5:229,APP6:230,APP7:231,APP8:232,APP9:233,APP10:234,APP11:235,APP12:236,APP13:237,APP14:238,COM:254};return{getExifBytesFromJpeg:function(t){return new Promise(function(i,n){if(!(t instanceof Blob||t instanceof File))return n(new TypeError("The argument must be a Blob or a File"));var a=new FileReader;a.addEventListener("error",function(){a.abort(),n(a.error)}),a.addEventListener("load",function(){var t=a.result,r=new Uint8Array(t),o=new Uint8Array;if(255!==r[0]&&r[1]!==e.SOI)return n(new Error("Not a JPEG"));for(var s=2;s<r.length&&255===r[s];){var l=2+(r[s+2]<<8|r[s+3]);if(r[s+1]===e.APP1){for(var c="",d=s+4;0!==r[d]&&d<r.length;d++)c+=String.fromCharCode(r[d]);if("Exif"===c||"http://ns.adobe.com/xap/1.0/"===c){var u=Array.prototype.slice.call(r,s,l+s),h=new Uint8Array(o.length+u.length);h.set(o),h.set(u,o.length),o=h}}s+=l}i(o)}),a.readAsArrayBuffer(t)})},removeExifData:function(t){return new Promise(function(i,n){if(!(t instanceof Blob||t instanceof File))return n(new TypeError("The argument must be a Blob or a File"));var a=new FileReader;a.addEventListener("error",function(){a.abort(),n(a.error)}),a.addEventListener("load",function(){var r=a.result,o=new Uint8Array(r);if(255!==o[0]&&o[1]!==e.SOI)return n(new Error("Not a JPEG"));for(var s=2;s<o.length&&255===o[s];){var l=2+(o[s+2]<<8|o[s+3]);if(o[s+1]===e.APP1){for(var c="",d=s+4;0!==o[d]&&d<o.length;d++)c+=String.fromCharCode(o[d]);if("Exif"===c||"http://ns.adobe.com/xap/1.0/"===c){var u=Array.prototype.slice.call(o,0,s),h=Array.prototype.slice.call(o,s+l);o=new Uint8Array(u.length+h.length),o.set(u,0),o.set(h,u.length)}}else s+=l}i(new Blob([o],{type:t.type}))}),a.readAsArrayBuffer(t)})},setExifData:function(t,i){return this.removeExifData(t).then(function(t){return new Promise(function(n){var a=new FileReader;a.addEventListener("error",function(){a.abort(),reject(a.error)}),a.addEventListener("load",function(){var r=a.result,o=new Uint8Array(r),s=2;255===o[2]&&o[3]===e.APP0&&(s+=2+(o[4]<<8|o[5]));var l=Array.prototype.slice.call(o,0,s),c=Array.prototype.slice.call(o,s);o=new Uint8Array(l.length+i.length+c.length),o.set(l),o.set(i,s),o.set(c,s+i.length),n(new Blob([o],{type:t.type}))}),a.readAsArrayBuffer(t)})})}}}),define("WoltLabSuite/Core/Image/ImageUtil",[],function(){"use strict";return{containsTransparentPixels:function(e){for(var t=e.getContext("2d").getImageData(0,0,e.width,e.height),i=3,n=t.data.length;i<n;i+=4)if(255!==t.data[i])return!0;return!1}}}),function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define("Pica",[],e);else{var t;t="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,t.pica=e()}}(function(){return function(){function e(t,i,n){function a(o,s){if(!i[o]){if(!t[o]){var l="function"==typeof require&&require;if(!s&&l)return l(o,!0);if(r)return r(o,!0);var c=new Error("Cannot find module '"+o+"'");throw c.code="MODULE_NOT_FOUND",c}var d=i[o]={exports:{}};t[o][0].call(d.exports,function(e){return a(t[o][1][e]||e)},d,d.exports,e,t,i,n)}return i[o].exports}for(var r="function"==typeof require&&require,o=0;o<n.length;o++)a(n[o]);return a}return e}()({1:[function(e,t,i){"use strict";function n(e){var t=e||[],i={js:t.indexOf("js")>=0,wasm:t.indexOf("wasm")>=0};r.call(this,i),this.features={js:i.js,wasm:i.wasm&&this.has_wasm},this.use(o),this.use(s)}var a=e("inherits"),r=e("multimath"),o=e("multimath/lib/unsharp_mask"),s=e("./mm_resize");a(n,r),n.prototype.resizeAndUnsharp=function(e,t){var i=this.resize(e,t);return e.unsharpAmount&&this.unsharp_mask(i,e.toWidth,e.toHeight,e.unsharpAmount,e.unsharpRadius,e.unsharpThreshold),i},t.exports=n},{"./mm_resize":4,inherits:15,multimath:16,"multimath/lib/unsharp_mask":19}],2:[function(e,t,i){"use strict";function n(e){return e<0?0:e>255?255:e}function a(e,t,i,a,r,o){var s,l,c,d,u,h,p,f,m,g,v,_=0,b=0;for(m=0;m<a;m++){for(u=0,g=0;g<r;g++){for(h=o[u++],p=o[u++],f=_+4*h|0,s=l=c=d=0;p>0;p--)v=o[u++],d=d+v*e[f+3]|0,c=c+v*e[f+2]|0,l=l+v*e[f+1]|0,s=s+v*e[f]|0,f=f+4|0;t[b+3]=n(d+8192>>14),t[b+2]=n(c+8192>>14),t[b+1]=n(l+8192>>14),t[b]=n(s+8192>>14),b=b+4*a|0}b=4*(m+1)|0,_=(m+1)*i*4|0}}function r(e,t,i,a,r,o){var s,l,c,d,u,h,p,f,m,g,v,_=0,b=0;for(m=0;m<a;m++){for(u=0,g=0;g<r;g++){for(h=o[u++],p=o[u++],f=_+4*h|0,s=l=c=d=0;p>0;p--)v=o[u++],d=d+v*e[f+3]|0,c=c+v*e[f+2]|0,l=l+v*e[f+1]|0,s=s+v*e[f]|0,f=f+4|0;t[b+3]=n(d+8192>>14),t[b+2]=n(c+8192>>14),t[b+1]=n(l+8192>>14),t[b]=n(s+8192>>14),b=b+4*a|0}b=4*(m+1)|0,_=(m+1)*i*4|0}}t.exports={convolveHorizontally:a,convolveVertically:r}},{}],3:[function(e,t,i){"use strict"
+;t.exports="AGFzbQEAAAABFAJgBn9/f39/fwBgB39/f39/f38AAg8BA2VudgZtZW1vcnkCAAEDAwIAAQQEAXAAAAcZAghjb252b2x2ZQAACmNvbnZvbHZlSFYAAQkBAArmAwLBAwEQfwJAIANFDQAgBEUNACAFQQRqIRVBACEMQQAhDQNAIA0hDkEAIRFBACEHA0AgB0ECaiESAn8gBSAHQQF0IgdqIgZBAmouAQAiEwRAQQAhCEEAIBNrIRQgFSAHaiEPIAAgDCAGLgEAakECdGohEEEAIQlBACEKQQAhCwNAIBAoAgAiB0EYdiAPLgEAIgZsIAtqIQsgB0H/AXEgBmwgCGohCCAHQRB2Qf8BcSAGbCAKaiEKIAdBCHZB/wFxIAZsIAlqIQkgD0ECaiEPIBBBBGohECAUQQFqIhQNAAsgEiATagwBC0EAIQtBACEKQQAhCUEAIQggEgshByABIA5BAnRqIApBgMAAakEOdSIGQf8BIAZB/wFIG0EQdEGAgPwHcUEAIAZBAEobIAtBgMAAakEOdSIGQf8BIAZB/wFIG0EYdEEAIAZBAEobciAJQYDAAGpBDnUiBkH/ASAGQf8BSBtBCHRBgP4DcUEAIAZBAEobciAIQYDAAGpBDnUiBkH/ASAGQf8BSBtB/wFxQQAgBkEAShtyNgIAIA4gA2ohDiARQQFqIhEgBEcNAAsgDCACaiEMIA1BAWoiDSADRw0ACwsLIQACQEEAIAIgAyAEIAUgABAAIAJBACAEIAUgBiABEAALCw=="},{}],4:[function(e,t,i){"use strict";t.exports={name:"resize",fn:e("./resize"),wasm_fn:e("./resize_wasm"),wasm_src:e("./convolve_wasm_base64")}},{"./convolve_wasm_base64":3,"./resize":5,"./resize_wasm":8}],5:[function(e,t,i){"use strict";function n(e,t,i){for(var n=3,a=t*i*4|0;n<a;)e[n]=255,n=n+4|0}var a=e("./resize_filter_gen"),r=e("./convolve").convolveHorizontally,o=e("./convolve").convolveVertically;t.exports=function(e){var t=e.src,i=e.width,s=e.height,l=e.toWidth,c=e.toHeight,d=e.scaleX||e.toWidth/e.width,u=e.scaleY||e.toHeight/e.height,h=e.offsetX||0,p=e.offsetY||0,f=e.dest||new Uint8Array(l*c*4),m=void 0===e.quality?3:e.quality,g=e.alpha||!1,v=a(m,i,l,d,h),_=a(m,s,c,u,p),b=new Uint8Array(l*s*4);return r(t,b,i,s,l,v),o(b,f,s,l,c,_),g||n(f,l,c),f}},{"./convolve":2,"./resize_filter_gen":6}],6:[function(e,t,i){"use strict";function n(e){return Math.round(e*((1<<r)-1))}var a=e("./resize_filter_info"),r=14;t.exports=function(e,t,i,r,o){var s,l,c,d,u,h,p,f,m,g,v,_,b,w,y,C,E,L=a[e].filter,I=1/r,S=Math.min(1,r),A=a[e].win/S,D=Math.floor(2*(A+1)),x=new Int16Array((D+2)*i),T=0,B=!x.subarray||!x.set;for(s=0;s<i;s++){for(l=(s+.5)*I+o,c=Math.max(0,Math.floor(l-A)),d=Math.min(t-1,Math.ceil(l+A)),u=d-c+1,h=new Float32Array(u),p=new Int16Array(u),f=0,m=c,g=0;m<=d;m++,g++)v=L((m+.5-l)*S),f+=v,h[g]=v;for(_=0,g=0;g<h.length;g++)b=h[g]/f,_+=b,p[g]=n(b);for(p[i>>1]+=n(1-_),w=0;w<p.length&&0===p[w];)w++;if(w<p.length){for(y=p.length-1;y>0&&0===p[y];)y--;if(C=c+w,E=y-w+1,x[T++]=C,x[T++]=E,B)for(g=w;g<=y;g++)x[T++]=p[g];else x.set(p.subarray(w,y+1),T),T+=E}else x[T++]=0,x[T++]=0}return x}},{"./resize_filter_info":7}],7:[function(e,t,i){"use strict";t.exports=[{win:.5,filter:function(e){return e>=-.5&&e<.5?1:0}},{win:1,filter:function(e){if(e<=-1||e>=1)return 0;if(e>-1.1920929e-7&&e<1.1920929e-7)return 1;var t=e*Math.PI;return Math.sin(t)/t*(.54+.46*Math.cos(t/1))}},{win:2,filter:function(e){if(e<=-2||e>=2)return 0;if(e>-1.1920929e-7&&e<1.1920929e-7)return 1;var t=e*Math.PI;return Math.sin(t)/t*Math.sin(t/2)/(t/2)}},{win:3,filter:function(e){if(e<=-3||e>=3)return 0;if(e>-1.1920929e-7&&e<1.1920929e-7)return 1;var t=e*Math.PI;return Math.sin(t)/t*Math.sin(t/3)/(t/3)}}]},{}],8:[function(e,t,i){"use strict";function n(e,t,i){for(var n=3,a=t*i*4|0;n<a;)e[n]=255,n=n+4|0}function a(e){return new Uint8Array(e.buffer,0,e.byteLength)}function r(e,t,i){if(s)return void t.set(a(e),i);for(var n=i,r=0;r<e.length;r++){var o=e[r];t[n++]=255&o,t[n++]=o>>8&255}}var o=e("./resize_filter_gen"),s=!0;try{s=1===new Uint32Array(new Uint8Array([1,0,0,0]).buffer)[0]}catch(e){}t.exports=function(e){var t=e.src,i=e.width,a=e.height,s=e.toWidth,l=e.toHeight,c=e.scaleX||e.toWidth/e.width,d=e.scaleY||e.toHeight/e.height,u=e.offsetX||0,h=e.offsetY||0,p=e.dest||new Uint8Array(s*l*4),f=void 0===e.quality?3:e.quality,m=e.alpha||!1,g=o(f,i,s,c,u),v=o(f,a,l,d,h),_=this.__align(0+Math.max(t.byteLength,p.byteLength)),b=this.__align(_+a*s*4),w=this.__align(b+g.byteLength),y=w+v.byteLength,C=this.__instance("resize",y),E=new Uint8Array(this.__memory.buffer),L=new Uint32Array(this.__memory.buffer),I=new Uint32Array(t.buffer);return L.set(I),r(g,E,b),r(v,E,w),(C.exports.convolveHV||C.exports._convolveHV)(b,w,_,i,a,s,l),new Uint32Array(p.buffer).set(new Uint32Array(this.__memory.buffer,0,l*s)),m||n(p,s,l),p}},{"./resize_filter_gen":6}],9:[function(e,t,i){"use strict";function n(e,t){this.create=e,this.available=[],this.acquired={},this.lastId=1,this.timeoutId=0,this.idle=t||2e3}n.prototype.acquire=function(){var e,t=this;return 0!==this.available.length?e=this.available.pop():(e=this.create(),e.id=this.lastId++,e.release=function(){return t.release(e)}),this.acquired[e.id]=e,e},n.prototype.release=function(e){var t=this;delete this.acquired[e.id],e.lastUsed=Date.now(),this.available.push(e),0===this.timeoutId&&(this.timeoutId=setTimeout(function(){return t.gc()},100))},n.prototype.gc=function(){var e=this,t=Date.now();this.available=this.available.filter(function(i){return!(t-i.lastUsed>e.idle)||(i.destroy(),!1)}),0!==this.available.length?this.timeoutId=setTimeout(function(){return e.gc()},100):this.timeoutId=0},t.exports=n},{}],10:[function(e,t,i){"use strict";t.exports=function(e,t,i,n,a,r){var o=i/e,s=n/t,l=(2*r+2+1)/a;if(l>.5)return[[i,n]];var c=Math.ceil(Math.log(Math.min(o,s))/Math.log(l));if(c<=1)return[[i,n]];for(var d=[],u=0;u<c;u++){var h=Math.round(Math.pow(Math.pow(e,c-u-1)*Math.pow(i,u+1),1/c)),p=Math.round(Math.pow(Math.pow(t,c-u-1)*Math.pow(n,u+1),1/c));d.push([h,p])}return d}},{}],11:[function(e,t,i){"use strict";function n(e){var t=Math.round(e);return Math.abs(e-t)<r?t:Math.floor(e)}function a(e){var t=Math.round(e);return Math.abs(e-t)<r?t:Math.ceil(e)}var r=1e-5;t.exports=function(e){var t=e.toWidth/e.width,i=e.toHeight/e.height,r=n(e.srcTileSize*t)-2*e.destTileBorder,o=n(e.srcTileSize*i)-2*e.destTileBorder;if(r<1||o<1)throw new Error("Internal error in pica: target tile width/height is too small.");var s,l,c,d,u,h,p,f=[];for(d=0;d<e.toHeight;d+=o)for(c=0;c<e.toWidth;c+=r)s=c-e.destTileBorder,s<0&&(s=0),u=c+r+e.destTileBorder-s,s+u>=e.toWidth&&(u=e.toWidth-s),l=d-e.destTileBorder,l<0&&(l=0),h=d+o+e.destTileBorder-l,l+h>=e.toHeight&&(h=e.toHeight-l),p={toX:s,toY:l,toWidth:u,toHeight:h,toInnerX:c,toInnerY:d,toInnerWidth:r,toInnerHeight:o,offsetX:s/t-n(s/t),offsetY:l/i-n(l/i),scaleX:t,scaleY:i,x:n(s/t),y:n(l/i),width:a(u/t),height:a(h/i)},f.push(p);return f}},{}],12:[function(e,t,i){"use strict";function n(e){return Object.prototype.toString.call(e)}t.exports.isCanvas=function(e){var t=n(e);return"[object HTMLCanvasElement]"===t||"[object Canvas]"===t},t.exports.isImage=function(e){return"[object HTMLImageElement]"===n(e)},t.exports.limiter=function(e){function t(){i<e&&n.length&&(i++,n.shift()())}var i=0,n=[];return function(e){return new Promise(function(a,r){n.push(function(){e().then(function(e){a(e),i--,t()},function(e){r(e),i--,t()})}),t()})}},t.exports.cib_quality_name=function(e){switch(e){case 0:return"pixelated";case 1:return"low";case 2:return"medium"}return"high"},t.exports.cib_support=function(){return Promise.resolve().then(function(){if("undefined"==typeof createImageBitmap||"undefined"==typeof document)return!1;var e=document.createElement("canvas");return e.width=100,e.height=100,createImageBitmap(e,0,0,100,100,{resizeWidth:10,resizeHeight:10,resizeQuality:"high"}).then(function(t){var i=10===t.width;return t.close(),e=null,i})}).catch(function(){return!1})}},{}],13:[function(e,t,i){"use strict";t.exports=function(){var t,i=e("./mathlib");onmessage=function(e){var n=e.data.opts;t||(t=new i(e.data.features));var a=t.resizeAndUnsharp(n);postMessage({result:a},[a.buffer])}}},{"./mathlib":1}],14:[function(e,t,i){function n(e){e<.5&&(e=.5);var t=Math.exp(.527076)/e,i=Math.exp(-t),n=Math.exp(-2*t),a=(1-i)*(1-i)/(1+2*t*i-n);return o=a,s=a*(t-1)*i,l=a*(t+1)*i,c=-a*n,d=2*i,u=-n,h=(o+s)/(1-d-u),p=(l+c)/(1-d-u),new Float32Array([o,s,l,c,d,u,h,p])}function a(e,t,i,n,a,r){var o,s,l,c,d,u,h,p,f,m,g,v,_,b;for(f=0;f<r;f++){for(u=f*a,h=f,p=0,o=e[u],d=o*n[6],c=d,g=n[0],v=n[1],_=n[4],b=n[5],m=0;m<a;m++)s=e[u],l=s*g+o*v+c*_+d*b,d=c,c=l,o=s,i[p]=c,p++,u++;for(u--,p--,h+=r*(a-1),o=e[u],d=o*n[7],c=d,s=o,g=n[2],v=n[3],m=a-1;m>=0;m--)l=s*g+o*v+c*_+d*b,d=c,c=l,o=s,s=e[u],t[h]=i[p]+c,u--,p--,h-=r}}function r(e,t,i,r){if(r){var o=new Uint16Array(e.length),s=new Float32Array(Math.max(t,i)),l=n(r);a(e,o,s,l,t,i,r),a(o,e,s,l,i,t,r)}}var o,s,l,c,d,u,h,p;t.exports=r},{}],15:[function(e,t,i){"function"==typeof Object.create?t.exports=function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})}:t.exports=function(e,t){e.super_=t;var i=function(){};i.prototype=t.prototype,e.prototype=new i,e.prototype.constructor=e}},{}],16:[function(e,t,i){"use strict";function n(e){if(!(this instanceof n))return new n(e);var t=a({},s,e||{});if(this.options=t,this.__cache={},this.has_wasm=o(),this.__init_promise=null,this.__modules=t.modules||{},this.__memory=null,this.__wasm={},this.__isLE=1===new Uint32Array(new Uint8Array([1,0,0,0]).buffer)[0],!this.options.js&&!this.options.wasm)throw new Error('mathlib: at least "js" or "wasm" should be enabled')}var a=e("object-assign"),r=e("./lib/base64decode"),o=e("./lib/wa_detect"),s={js:!0,wasm:!0};n.prototype.use=function(e){return this.__modules[e.name]=e,this.has_wasm&&this.options.wasm&&e.wasm_fn?this[e.name]=e.wasm_fn:this[e.name]=e.fn,this},n.prototype.init=function(){if(this.__init_promise)return this.__init_promise;if(!this.options.js&&this.options.wasm&&!this.has_wasm)return Promise.reject(new Error('mathlib: only "wasm" was enabled, but it\'s not supported'));var e=this;return this.__init_promise=Promise.all(Object.keys(e.__modules).map(function(t){var i=e.__modules[t];return e.has_wasm&&e.options.wasm&&i.wasm_fn?e.__wasm[t]?null:WebAssembly.compile(e.__base64decode(i.wasm_src)).then(function(i){e.__wasm[t]=i}):null})).then(function(){return e}),this.__init_promise},n.prototype.__base64decode=r,n.prototype.__reallocate=function(e){if(!this.__memory)return this.__memory=new WebAssembly.Memory({initial:Math.ceil(e/65536)}),this.__memory;var t=this.__memory.buffer.byteLength;return t<e&&this.__memory.grow(Math.ceil((e-t)/65536)),this.__memory},n.prototype.__instance=function(e,t,i){if(t&&this.__reallocate(t),!this.__wasm[e]){var n=this.__modules[e];this.__wasm[e]=new WebAssembly.Module(this.__base64decode(n.wasm_src))}if(!this.__cache[e]){var r={memoryBase:0,memory:this.__memory,tableBase:0,table:new WebAssembly.Table({initial:0,element:"anyfunc"})};this.__cache[e]=new WebAssembly.Instance(this.__wasm[e],{env:a(r,i||{})})}return this.__cache[e]},n.prototype.__align=function(e,t){t=t||8;var i=e%t;return e+(i?t-i:0)},t.exports=n},{"./lib/base64decode":17,"./lib/wa_detect":23,"object-assign":24}],17:[function(e,t,i){"use strict";t.exports=function(e){for(var t=e.replace(/[\r\n=]/g,""),i=t.length,n=new Uint8Array(3*i>>2),a=0,r=0,o=0;o<i;o++)o%4==0&&o&&(n[r++]=a>>16&255,n[r++]=a>>8&255,n[r++]=255&a),a=a<<6|"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".indexOf(t.charAt(o));var s=i%4*6;return 0===s?(n[r++]=a>>16&255,n[r++]=a>>8&255,n[r++]=255&a):18===s?(n[r++]=a>>10&255,n[r++]=a>>2&255):12===s&&(n[r++]=a>>4&255),n}},{}],18:[function(e,t,i){"use strict";t.exports=function(e,t,i){for(var n,a,r,o,s,l=t*i,c=new Uint16Array(l),d=0;d<l;d++)n=e[4*d],a=e[4*d+1],r=e[4*d+2],s=n>=a&&n>=r?n:a>=r&&a>=n?a:r,o=n<=a&&n<=r?n:a<=r&&a<=n?a:r,c[d]=257*(s+o)>>1;return c}},{}],19:[function(e,t,i){"use strict";t.exports={name:"unsharp_mask",fn:e("./unsharp_mask"),wasm_fn:e("./unsharp_mask_wasm"),wasm_src:e("./unsharp_mask_wasm_base64")}},{"./unsharp_mask":20,"./unsharp_mask_wasm":21,"./unsharp_mask_wasm_base64":22}],20:[function(e,t,i){"use strict";var n=e("glur/mono16"),a=e("./hsl_l16");t.exports=function(e,t,i,r,o,s){var l,c,d,u,h,p,f,m,g,v,_,b,w;if(!(0===r||o<.5)){o>2&&(o=2);var y=a(e,t,i),C=new Uint16Array(y);n(C,t,i,o);for(var E=r/100*4096+.5|0,L=257*s|0,I=t*i,S=0;S<I;S++)b=2*(y[S]-C[S]),Math.abs(b)>=L&&(w=4*S,l=e[w],c=e[w+1],d=e[w+2],m=l>=c&&l>=d?l:c>=l&&c>=d?c:d,f=l<=c&&l<=d?l:c<=l&&c<=d?c:d,p=257*(m+f)>>1,f===m?u=h=0:(h=p<=32767?4095*(m-f)/(m+f)|0:4095*(m-f)/(510-m-f)|0,u=l===m?65535*(c-d)/(6*(m-f))|0:c===m?21845+(65535*(d-l)/(6*(m-f))|0):43690+(65535*(l-c)/(6*(m-f))|0)),p+=E*b+2048>>12,p>65535?p=65535:p<0&&(p=0),0===h?l=c=d=p>>8:(v=p<=32767?p*(4096+h)+2048>>12:p+((65535-p)*h+2048>>12),g=2*p-v>>8,v>>=8,_=u+21845&65535,l=_>=43690?g:_>=32767?g+(6*(v-g)*(43690-_)+32768>>16):_>=10922?v:g+(6*(v-g)*_+32768>>16),_=65535&u,c=_>=43690?g:_>=32767?g+(6*(v-g)*(43690-_)+32768>>16):_>=10922?v:g+(6*(v-g)*_+32768>>16),_=u-21845&65535,d=_>=43690?g:_>=32767?g+(6*(v-g)*(43690-_)+32768>>16):_>=10922?v:g+(6*(v-g)*_+32768>>16)),e[w]=l,e[w+1]=c,e[w+2]=d)}}},{"./hsl_l16":18,"glur/mono16":14}],21:[function(e,t,i){"use strict";t.exports=function(e,t,i,n,a,r){if(!(0===n||a<.5)){a>2&&(a=2);var o=t*i,s=4*o,l=2*o,c=2*o,d=4*Math.max(t,i),u=s,h=u+l,p=h+c,f=p+c,m=f+d,g=this.__instance("unsharp_mask",s+l+2*c+d+32,{exp:Math.exp}),v=new Uint32Array(e.buffer);new Uint32Array(this.__memory.buffer).set(v);var _=g.exports.hsl_l16||g.exports._hsl_l16;_(0,u,t,i),_=g.exports.blurMono16||g.exports._blurMono16,_(u,h,p,f,m,t,i,a),_=g.exports.unsharp||g.exports._unsharp,_(0,0,u,h,t,i,n,r),v.set(new Uint32Array(this.__memory.buffer,0,o))}}},{}],22:[function(e,t,i){"use strict";t.exports="AGFzbQEAAAABMQZgAXwBfGACfX8AYAZ/f39/f38AYAh/f39/f39/fQBgBH9/f38AYAh/f39/f39/fwACGQIDZW52A2V4cAAAA2VudgZtZW1vcnkCAAEDBgUBAgMEBQQEAXAAAAdMBRZfX2J1aWxkX2dhdXNzaWFuX2NvZWZzAAEOX19nYXVzczE2X2xpbmUAAgpibHVyTW9ubzE2AAMHaHNsX2wxNgAEB3Vuc2hhcnAABQkBAAqJEAXZAQEGfAJAIAFE24a6Q4Ia+z8gALujIgOaEAAiBCAEoCIGtjgCECABIANEAAAAAAAAAMCiEAAiBbaMOAIUIAFEAAAAAAAA8D8gBKEiAiACoiAEIAMgA6CiRAAAAAAAAPA/oCAFoaMiArY4AgAgASAEIANEAAAAAAAA8L+gIAKioiIHtjgCBCABIAQgA0QAAAAAAADwP6AgAqKiIgO2OAIIIAEgBSACoiIEtow4AgwgASACIAegIAVEAAAAAAAA8D8gBqGgIgKjtjgCGCABIAMgBKEgAqO2OAIcCwu3AwMDfwR9CHwCQCADKgIUIQkgAyoCECEKIAMqAgwhCyADKgIIIQwCQCAEQX9qIgdBAEgiCA0AIAIgAC8BALgiDSADKgIYu6IiDiAJuyIQoiAOIAq7IhGiIA0gAyoCBLsiEqIgAyoCALsiEyANoqCgoCIPtjgCACACQQRqIQIgAEECaiEAIAdFDQAgBCEGA0AgAiAOIBCiIA8iDiARoiANIBKiIBMgAC8BALgiDaKgoKAiD7Y4AgAgAkEEaiECIABBAmohACAGQX9qIgZBAUoNAAsLAkAgCA0AIAEgByAFbEEBdGogAEF+ai8BACIIuCINIAu7IhGiIA0gDLsiEqKgIA0gAyoCHLuiIg4gCrsiE6KgIA4gCbsiFKKgIg8gAkF8aioCALugqzsBACAHRQ0AIAJBeGohAiAAQXxqIQBBACAFQQF0ayEHIAEgBSAEQQF0QXxqbGohBgNAIAghAyAALwEAIQggBiANIBGiIAO4Ig0gEqKgIA8iECAToqAgDiAUoqAiDyACKgIAu6CrOwEAIAYgB2ohBiAAQX5qIQAgAkF8aiECIBAhDiAEQX9qIgRBAUoNAAsLCwvfAgIDfwZ8AkAgB0MAAAAAWw0AIARE24a6Q4Ia+z8gB0MAAAA/l7ujIgyaEAAiDSANoCIPtjgCECAEIAxEAAAAAAAAAMCiEAAiDraMOAIUIAREAAAAAAAA8D8gDaEiCyALoiANIAwgDKCiRAAAAAAAAPA/oCAOoaMiC7Y4AgAgBCANIAxEAAAAAAAA8L+gIAuioiIQtjgCBCAEIA0gDEQAAAAAAADwP6AgC6KiIgy2OAIIIAQgDiALoiINtow4AgwgBCALIBCgIA5EAAAAAAAA8D8gD6GgIgujtjgCGCAEIAwgDaEgC6O2OAIcIAYEQCAFQQF0IQogBiEJIAIhCANAIAAgCCADIAQgBSAGEAIgACAKaiEAIAhBAmohCCAJQX9qIgkNAAsLIAVFDQAgBkEBdCEIIAUhAANAIAIgASADIAQgBiAFEAIgAiAIaiECIAFBAmohASAAQX9qIgANAAsLC7wBAQV/IAMgAmwiAwRAQQAgA2shBgNAIAAoAgAiBEEIdiIHQf8BcSECAn8gBEH/AXEiAyAEQRB2IgRB/wFxIgVPBEAgAyIIIAMgAk8NARoLIAQgBCAHIAIgA0kbIAIgBUkbQf8BcQshCAJAIAMgAk0EQCADIAVNDQELIAQgByAEIAMgAk8bIAIgBUsbQf8BcSEDCyAAQQRqIQAgASADIAhqQYECbEEBdjsBACABQQJqIQEgBkEBaiIGDQALCwvTBgEKfwJAIAazQwAAgEWUQwAAyEKVu0QAAAAAAADgP6CqIQ0gBSAEbCILBEAgB0GBAmwhDgNAQQAgAi8BACADLwEAayIGQQF0IgdrIAcgBkEASBsgDk8EQCAAQQJqLQAAIQUCfyAALQAAIgYgAEEBai0AACIESSIJRQRAIAYiCCAGIAVPDQEaCyAFIAUgBCAEIAVJGyAGIARLGwshCAJ/IAYgBE0EQCAGIgogBiAFTQ0BGgsgBSAFIAQgBCAFSxsgCRsLIgogCGoiD0GBAmwiEEEBdiERQQAhDAJ/QQAiCSAIIApGDQAaIAggCmsiCUH/H2wgD0H+AyAIayAKayAQQYCABEkbbSEMIAYgCEYEQCAEIAVrQf//A2wgCUEGbG0MAQsgBSAGayAGIARrIAQgCEYiBhtB//8DbCAJQQZsbUHVqgFBqtUCIAYbagshCSARIAcgDWxBgBBqQQx1aiIGQQAgBkEAShsiBkH//wMgBkH//wNIGyEGAkACfwJAIAxB//8DcSIFBEAgBkH//wFKDQEgBUGAIGogBmxBgBBqQQx2DAILIAZBCHYiBiEFIAYhBAwCCyAFIAZB//8Dc2xBgBBqQQx2IAZqCyIFQQh2IQcgBkEBdCAFa0EIdiIGIQQCQCAJQdWqAWpB//8DcSIFQanVAksNACAFQf//AU8EQEGq1QIgBWsgByAGa2xBBmxBgIACakEQdiAGaiEEDAELIAchBCAFQanVAEsNACAFIAcgBmtsQQZsQYCAAmpBEHYgBmohBAsCfyAGIgUgCUH//wNxIghBqdUCSw0AGkGq1QIgCGsgByAGa2xBBmxBgIACakEQdiAGaiAIQf//AU8NABogByIFIAhBqdUASw0AGiAIIAcgBmtsQQZsQYCAAmpBEHYgBmoLIQUgCUGr1QJqQf//A3EiCEGp1QJLDQAgCEH//wFPBEBBqtUCIAhrIAcgBmtsQQZsQYCAAmpBEHYgBmohBgwBCyAIQanVAEsEQCAHIQYMAQsgCCAHIAZrbEEGbEGAgAJqQRB2IAZqIQYLIAEgBDoAACABQQFqIAU6AAAgAUECaiAGOgAACyADQQJqIQMgAkECaiECIABBBGohACABQQRqIQEgC0F/aiILDQALCwsL"},{}],23:[function(e,t,i){"use strict";var n;t.exports=function(){if(void 0!==n)return n;if(n=!1,"undefined"==typeof WebAssembly)return n;try{var e=new Uint8Array([0,97,115,109,1,0,0,0,1,6,1,96,1,127,1,127,3,2,1,0,5,3,1,0,1,7,8,1,4,116,101,115,116,0,0,10,16,1,14,0,32,0,65,1,54,2,0,32,0,40,2,0,11]),t=new WebAssembly.Module(e);return 0!==new WebAssembly.Instance(t,{}).exports.test(4)&&(n=!0),n}catch(e){}return n}},{}],24:[function(e,t,i){"use strict";function n(e){if(null===e||void 0===e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}var a=Object.getOwnPropertySymbols,r=Object.prototype.hasOwnProperty,o=Object.prototype.propertyIsEnumerable;t.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},i=0;i<10;i++)t["_"+String.fromCharCode(i)]=i;if("0123456789"!==Object.getOwnPropertyNames(t).map(function(e){return t[e]}).join(""))return!1;var n={};return"abcdefghijklmnopqrst".split("").forEach(function(e){n[e]=e}),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},n)).join("")}catch(e){return!1}}()?Object.assign:function(e,t){for(var i,s,l=n(e),c=1;c<arguments.length;c++){i=Object(arguments[c]);for(var d in i)r.call(i,d)&&(l[d]=i[d]);if(a){s=a(i);for(var u=0;u<s.length;u++)o.call(i,s[u])&&(l[s[u]]=i[s[u]])}}return l}},{}],25:[function(e,t,i){var n=arguments[3],a=arguments[4],r=arguments[5],o=JSON.stringify;t.exports=function(e,t){function i(e){g[e]=!0;for(var t in a[e][1]){var n=a[e][1][t];g[n]||i(n)}}for(var s,l=Object.keys(r),c=0,d=l.length;c<d;c++){var u=l[c],h=r[u].exports;if(h===e||h&&h.default===e){s=u;break}}if(!s){s=Math.floor(Math.pow(16,8)*Math.random()).toString(16);for(var p={},c=0,d=l.length;c<d;c++){var u=l[c];p[u]=u}a[s]=["function(require,module,exports){"+e+"(self); }",p]}var f=Math.floor(Math.pow(16,8)*Math.random()).toString(16),m={};m[s]=s,a[f]=["function(require,module,exports){var f = require("+o(s)+");(f.default ? f.default : f)(self);}",m];var g={};i(f);var v="("+n+")({"+Object.keys(g).map(function(e){return o(e)+":["+a[e][0]+","+o(a[e][1])+"]"}).join(",")+"},{},["+o(f)+"])",_=window.URL||window.webkitURL||window.mozURL||window.msURL,b=new Blob([v],{type:"text/javascript"});if(t&&t.bare)return b;var w=_.createObjectURL(b),y=new Worker(w);return y.objectURL=w,y}},{}],"/":[function(e,t,i){"use strict";function n(e,t){return o(e)||r(e,t)||a()}function a(){throw new TypeError("Invalid attempt to destructure non-iterable instance")}function r(e,t){var i=[],n=!0,a=!1,r=void 0;try{for(var o,s=e[Symbol.iterator]();!(n=(o=s.next()).done)&&(i.push(o.value),!t||i.length!==t);n=!0);}catch(e){a=!0,r=e}finally{try{n||null==s.return||s.return()}finally{if(a)throw r}}return i}function o(e){if(Array.isArray(e))return e}function s(){return{value:d(f),destroy:function(){if(this.value.terminate(),"undefined"!=typeof window){var e=window.URL||window.webkitURL||window.mozURL||window.msURL;e&&e.revokeObjectURL&&this.value.objectURL&&e.revokeObjectURL(this.value.objectURL)}}}}function l(e){if(!(this instanceof l))return new l(e);this.options=c({},C,e||{});var t="lk_".concat(this.options.concurrency);this.__limit=v[t]||p.limiter(this.options.concurrency),v[t]||(v[t]=this.__limit),this.features={js:!1,wasm:!1,cib:!1,ww:!1},this.__workersPool=null,this.__requested_features=[],this.__mathlib=null}var c=e("object-assign"),d=e("webworkify"),u=e("./lib/mathlib"),h=e("./lib/pool"),p=e("./lib/utils"),f=e("./lib/worker"),m=e("./lib/stepper"),g=e("./lib/tiler"),v={},_=!1;try{"undefined"!=typeof navigator&&navigator.userAgent&&(_=navigator.userAgent.indexOf("Safari")>=0)}catch(e){}var b=1;"undefined"!=typeof navigator&&(b=Math.min(navigator.hardwareConcurrency||1,4));var w,y,C={tile:1024,concurrency:b,features:["js","wasm","ww"],idle:2e3},E={quality:3,alpha:!1,unsharpAmount:0,unsharpRadius:0,unsharpThreshold:0};l.prototype.init=function(){var t=this;if(this.__initPromise)return this.__initPromise;if(!1!==w&&!0!==w&&(w=!1,"undefined"!=typeof ImageData&&"undefined"!=typeof Uint8ClampedArray))try{new ImageData(new Uint8ClampedArray(400),10,10),w=!0}catch(e){}!1!==y&&!0!==y&&(y=!1,"undefined"!=typeof ImageBitmap&&(ImageBitmap.prototype&&ImageBitmap.prototype.close?y=!0:this.debug("ImageBitmap does not support .close(), disabled")));var i=this.options.features.slice();if(i.indexOf("all")>=0&&(i=["cib","wasm","js","ww"]),this.__requested_features=i,this.__mathlib=new u(i),i.indexOf("ww")>=0&&"undefined"!=typeof window&&"Worker"in window)try{e("webworkify")(function(){}).terminate(),this.features.ww=!0;var n="wp_".concat(JSON.stringify(this.options));v[n]?this.__workersPool=v[n]:(this.__workersPool=new h(s,this.options.idle),v[n]=this.__workersPool)}catch(e){}var a,r=this.__mathlib.init().then(function(e){c(t.features,e.features)});return a=y?p.cib_support().then(function(e){if(t.features.cib&&i.indexOf("cib")<0)return void t.debug("createImageBitmap() resize supported, but disabled by config");i.indexOf("cib")>=0&&(t.features.cib=e)}):Promise.resolve(!1),this.__initPromise=Promise.all([r,a]).then(function(){return t}),this.__initPromise},l.prototype.resize=function(e,t,i){var a=this;this.debug("Start resize...");var r=c({},E);if(isNaN(i)?i&&(r=c(r,i)):r=c(r,{quality:i}),r.toWidth=t.width,r.toHeight=t.height,r.width=e.naturalWidth||e.width,r.height=e.naturalHeight||e.height,0===t.width||0===t.height)return Promise.reject(new Error("Invalid output size: ".concat(t.width,"x").concat(t.height)));r.unsharpRadius>2&&(r.unsharpRadius=2);var o=!1,s=null;r.cancelToken&&(s=r.cancelToken.then(function(e){throw o=!0,e},function(e){throw o=!0,e}));var l=Math.ceil(Math.max(3,2.5*r.unsharpRadius|0));return this.init().then(function(){if(o)return s;if(a.features.cib){var i=t.getContext("2d",{alpha:Boolean(r.alpha)});return a.debug("Resize via createImageBitmap()"),createImageBitmap(e,{resizeWidth:r.toWidth,resizeHeight:r.toHeight,resizeQuality:p.cib_quality_name(r.quality)}).then(function(e){if(o)return s;if(!r.unsharpAmount)return i.drawImage(e,0,0),e.close(),i=null,a.debug("Finished!"),t;a.debug("Unsharp result");var n=document.createElement("canvas");n.width=r.toWidth,n.height=r.toHeight;var l=n.getContext("2d",{alpha:Boolean(r.alpha)});l.drawImage(e,0,0),e.close();var c=l.getImageData(0,0,r.toWidth,r.toHeight);return a.__mathlib.unsharp(c.data,r.toWidth,r.toHeight,r.unsharpAmount,r.unsharpRadius,r.unsharpThreshold),i.putImageData(c,0,0),c=l=n=i=null,a.debug("Finished!"),t})}var d={},u=function(e){return Promise.resolve().then(function(){return a.features.ww?new Promise(function(t,i){var n=a.__workersPool.acquire();s&&s.catch(function(e){return i(e)}),n.value.onmessage=function(e){n.release(),e.data.err?i(e.data.err):t(e.data.result)},n.value.postMessage({opts:e,features:a.__requested_features,preload:{wasm_nodule:a.__mathlib.__}},[e.src.buffer])}):a.__mathlib.resizeAndUnsharp(e,d)})},h=function(e,t,i){var n,r,c,d=function(t){return a.__limit(function(){if(o)return s;var l;if(p.isCanvas(e))a.debug("Get tile pixel data"),l=n.getImageData(t.x,t.y,t.width,t.height);else{a.debug("Draw tile imageBitmap/image to temporary canvas");var d=document.createElement("canvas");d.width=t.width,d.height=t.height;var h=d.getContext("2d",{alpha:Boolean(i.alpha)});h.globalCompositeOperation="copy",h.drawImage(r||e,t.x,t.y,t.width,t.height,0,0,t.width,t.height),a.debug("Get tile pixel data"),l=h.getImageData(0,0,t.width,t.height),h=d=null}var f={src:l.data,width:t.width,height:t.height,toWidth:t.toWidth,toHeight:t.toHeight,scaleX:t.scaleX,scaleY:t.scaleY,offsetX:t.offsetX,offsetY:t.offsetY,quality:i.quality,alpha:i.alpha,unsharpAmount:i.unsharpAmount,unsharpRadius:i.unsharpRadius,unsharpThreshold:i.unsharpThreshold};return a.debug("Invoke resize math"),Promise.resolve().then(function(){return u(f)}).then(function(e){if(o)return s;l=null;var i;if(a.debug("Convert raw rgba tile result to ImageData"),w)i=new ImageData(new Uint8ClampedArray(e),t.toWidth,t.toHeight);else if(i=c.createImageData(t.toWidth,t.toHeight),i.data.set)i.data.set(e);else for(var n=i.data.length-1;n>=0;n--)i.data[n]=e[n];return a.debug("Draw tile"),_?c.putImageData(i,t.toX,t.toY,t.toInnerX-t.toX,t.toInnerY-t.toY,t.toInnerWidth+1e-5,t.toInnerHeight+1e-5):c.putImageData(i,t.toX,t.toY,t.toInnerX-t.toX,t.toInnerY-t.toY,t.toInnerWidth,t.toInnerHeight),null})})};return Promise.resolve().then(function(){if(c=t.getContext("2d",{alpha:Boolean(i.alpha)}),p.isCanvas(e))return n=e.getContext("2d",{alpha:Boolean(i.alpha)}),null;if(p.isImage(e))return y?(a.debug("Decode image via createImageBitmap"),createImageBitmap(e).then(function(e){r=e})):null;throw new Error('".from" should be image or canvas')}).then(function(){function e(){r&&(r.close(),r=null)}if(o)return s;a.debug("Calculate tiles");var n=g({width:i.width,height:i.height,srcTileSize:a.options.tile,toWidth:i.toWidth,toHeight:i.toHeight,destTileBorder:l}),c=n.map(function(e){return d(e)});return a.debug("Process tiles"),Promise.all(c).then(function(){return a.debug("Finished!"),e(),t},function(t){throw e(),t})})},f=m(r.width,r.height,r.toWidth,r.toHeight,a.options.tile,l);return function e(t,i,a,r){if(o)return s;var l=t.shift(),d=n(l,2),u=d[0],p=d[1],f=0===t.length;r=c({},r,{toWidth:u,toHeight:p,quality:f?r.quality:Math.min(1,r.quality)});var m;return f||(m=document.createElement("canvas"),m.width=u,m.height=p),h(i,f?a:m,r).then(function(){return f?a:(r.width=u,r.height=p,e(t,m,a,r))})}(f,e,t,r)})},l.prototype.resizeBuffer=function(e){var t=this,i=c({},E,e);return this.init().then(function(){return t.__mathlib.resizeAndUnsharp(i)})},l.prototype.toBlob=function(e,t,i){return t=t||"image/png",new Promise(function(n){if(e.toBlob)return void e.toBlob(function(e){return n(e)},t,i);for(var a=atob(e.toDataURL(t,i).split(",")[1]),r=a.length,o=new Uint8Array(r),s=0;s<r;s++)o[s]=a.charCodeAt(s);n(new Blob([o],{type:t}))})},l.prototype.debug=function(){},t.exports=l},{"./lib/mathlib":1,"./lib/pool":9,"./lib/stepper":10,"./lib/tiler":11,"./lib/utils":12,"./lib/worker":13,"object-assign":24,webworkify:25}]},{},[])("/")}),define("WoltLabSuite/Core/Image/Resizer",["WoltLabSuite/Core/FileUtil","WoltLabSuite/Core/Image/ExifUtil","Pica"],function(e,t,i){"use strict";function n(){}var a=new i({features:["js","wasm","ww"]});return n.prototype={maxWidth:800,maxHeight:600,quality:.8,fileType:"image/jpeg",setMaxWidth:function(e){return null==e&&(e=n.prototype.maxWidth),this.maxWidth=e,this},setMaxHeight:function(e){return null==e&&(e=n.prototype.maxHeight),this.maxHeight=e,this},setQuality:function(e){return null==e&&(e=n.prototype.quality),this.quality=e,this},setFileType:function(e){return null==e&&(e=n.prototype.fileType),this.fileType=e,this},saveFile:function(i,n,r,o){r=r||this.fileType,o=o||this.quality;var s=n.match(/(.+)(\..+?)$/);return a.toBlob(i.image,r,o).then(function(e){return"image/jpeg"===r&&void 0!==i.exif?t.setExifData(e,i.exif):e}).then(function(t){return e.blobToFile(t,s[1]+"_autoscaled")})},loadFile:function(e){var i=void 0;"image/jpeg"===e.type&&(i=t.getExifBytesFromJpeg(e));var n=new Promise(function(t,i){var n=new FileReader,a=new Image;n.addEventListener("load",function(){a.src=n.result}),n.addEventListener("error",function(){n.abort(),i(n.error)}),a.addEventListener("error",i),a.addEventListener("load",function(){t(a)}),n.readAsDataURL(e)});return Promise.all([i,n]).then(function(e){return{exif:e[0],image:e[1]}})},resize:function(e,t,i,n,r,o){t=t||this.maxWidth,i=i||this.maxHeight,n=n||this.quality,r=r||!1;var s=document.createElement("canvas"),l=Math.min(t,e.width),c=Math.min(i,e.height);if(e.width<=l&&e.height<=c&&!r)return Promise.resolve(void 0);var d=Math.min(l/e.width,c/e.height);s.width=Math.floor(e.width*d),s.height=Math.floor(e.height*d);var u=1;n>=.8?u=3:n>=.4&&(u=2);var h={quality:u,cancelToken:o,alpha:!0};return a.resize(e,s,h)}},n}),define("WoltLabSuite/Core/Language/Chooser",["Dictionary","Language","Dom/Traverse","Dom/Util","ObjectMap","Ui/SimpleDropdown"],function(e,t,i,n,a,r){"use strict";var o=new e,s=!1,l=new a,c=null;return{init:function(e,t,i,n,a,r){if(!o.has(t)){var s=elById(e);if(null===s)throw new Error("Expected a valid container id, cannot find '"+t+"'.");var l=elById(t);null===l&&(l=elCreate("input"),elAttr(l,"type","hidden"),elAttr(l,"id",t),elAttr(l,"name",t),elAttr(l,"value",i),s.appendChild(l)),this._initElement(t,l,i,n,a,r)}},_setup:function(){s||(s=!0,c=this._submit.bind(this))},_initElement:function(e,a,s,d,u,h){var p;"DD"===a.parentNode.nodeName?(p=elCreate("div"),p.className="dropdown",n.prepend(p,a.parentNode)):(p=a.parentNode,p.classList.add("dropdown")),elHide(a);var f=elCreate("a");f.className="dropdownToggle dropdownIndicator boxFlag box24 inputPrefix"+("DD"===a.parentNode.nodeName?" button":""),p.appendChild(f);var m=elCreate("ul");m.className="dropdownMenu",p.appendChild(m);var g,v,_,b,w=function(t){var n=~~elData(t.currentTarget,"language-id"),a=i.childByClass(m,"active");null!==a&&a.classList.remove("active"),n&&t.currentTarget.classList.add("active"),this._select(e,n,t.currentTarget)}.bind(this);for(var y in d)if(d.hasOwnProperty(y)){var C=d[y];_=elCreate("li"),_.className="boxFlag",_.addEventListener(WCF_CLICK_EVENT,w),elData(_,"language-id",y),void 0!==C.languageCode&&elData(_,"language-code",C.languageCode),m.appendChild(_),g=elCreate("a"),g.className="box24",_.appendChild(g),v=elCreate("img"),elAttr(v,"src",C.iconPath),elAttr(v,"alt",""),v.className="iconFlag",g.appendChild(v),b=elCreate("span"),b.textContent=C.languageName,g.appendChild(b),y==s&&(f.innerHTML=_.firstChild.innerHTML)}if(h)_=elCreate("li"),_.className="dropdownDivider",m.appendChild(_),_=elCreate("li"),elData(_,"language-id",0),_.addEventListener(WCF_CLICK_EVENT,w),m.appendChild(_),g=elCreate("a"),g.textContent=t.get("wcf.global.language.noSelection"),_.appendChild(g),0===s&&(f.innerHTML=_.firstChild.innerHTML),_.addEventListener(WCF_CLICK_EVENT,w);else if(0===s){f.innerHTML=null;var E=elCreate("div");f.appendChild(E),b=elCreate("span"),b.className="icon icon24 fa-question",E.appendChild(b),b=elCreate("span"),b.textContent=t.get("wcf.global.language.noSelection"),E.appendChild(b)}r.init(f),o.set(e,{callback:u,dropdownMenu:m,dropdownToggle:f,element:a});var L=i.parentByTag(a,"FORM");if(null!==L){L.addEventListener("submit",c);var I=l.get(L);void 0===I&&(I=[],l.set(L,I)),I.push(e)}},_select:function(e,t,i){var n=o.get(e);if(void 0===i){for(var a=n.dropdownMenu.childNodes,r=0,s=a.length;r<s;r++){var l=a[r];if(~~elData(l,"language-id")===t){i=l;break}}if(void 0===i)throw new Error("Cannot select unknown language id '"+t+"'")}n.element.value=t,n.dropdownToggle.innerHTML=i.firstChild.innerHTML,o.set(e,n),"function"==typeof n.callback&&n.callback(i)},_submit:function(e){for(var t,i=l.get(e.currentTarget),n=0,a=i.length;n<a;n++)t=elCreate("input"),t.type="hidden",t.name=i[n],t.value=this.getLanguageId(i[n]),e.currentTarget.appendChild(t)},getChooser:function(e){var t=o.get(e);if(void 0===t)throw new Error("Expected a valid language chooser input element, '"+e+"' is not i18n input field.");return t},getLanguageId:function(e){return~~this.getChooser(e).element.value},removeChooser:function(e){o.has(e)&&o.delete(e)},setLanguageId:function(e,t){if(void 0===o.get(e))throw new Error("Expected a valid  input element, '"+e+"' is not i18n input field.");this._select(e,t)}}}),define("WoltLabSuite/Core/Language/Input",["Core","Dictionary","Language","ObjectMap","StringUtil","Dom/Traverse","Dom/Util","Ui/SimpleDropdown"],function(e,t,i,n,a,r,o,s){"use strict";var l=new t,c=!1,d=new n,u=new t,h=null,p=null;return{init:function(e,i,n,r){if(!u.has(e)){var o=elById(e);if(null===o)throw new Error("Expected a valid element id, cannot find '"+e+"'.");this._setup();var s=new t
+;for(var l in i)i.hasOwnProperty(l)&&s.set(~~l,a.unescapeHTML(i[l]));u.set(e,s),this._initElement(e,o,s,n,r)}},registerCallback:function(e,t,i){if(!u.has(e))throw new Error("Unknown element id '"+e+"'.");l.get(e).callbacks.set(t,i)},unregister:function(e){if(!u.has(e))throw new Error("Unknown element id '"+e+"'.");u.delete(e),l.delete(e)},_setup:function(){c||(c=!0,h=this._dropdownToggle.bind(this),p=this._submit.bind(this))},_initElement:function(e,n,a,c,u){var f=n.parentNode;if(!f.classList.contains("inputAddon")){f=elCreate("div"),f.className="inputAddon"+("TEXTAREA"===n.nodeName?" inputAddonTextarea":""),elData(f,"input-id",e);var m=document.activeElement===n;n.parentNode.insertBefore(f,n),f.appendChild(n),m&&n.focus()}f.classList.add("dropdown");var g=elCreate("span");g.className="button dropdownToggle inputPrefix";var v=elCreate("span");v.textContent=i.get("wcf.global.button.disabledI18n"),g.appendChild(v),f.insertBefore(g,n);var _=elCreate("ul");_.className="dropdownMenu",o.insertAfter(_,g);var b,w=function(t,i){var n=~~elData(t.currentTarget,"language-id"),a=r.childByClass(_,"active");null!==a&&a.classList.remove("active"),n&&t.currentTarget.classList.add("active"),this._select(e,n,i||!1)}.bind(this);for(var y in c)c.hasOwnProperty(y)&&(b=elCreate("li"),elData(b,"language-id",y),v=elCreate("span"),v.textContent=c[y],b.appendChild(v),b.addEventListener(WCF_CLICK_EVENT,w),_.appendChild(b));!0!==u&&(b=elCreate("li"),b.className="dropdownDivider",_.appendChild(b),b=elCreate("li"),elData(b,"language-id",0),v=elCreate("span"),v.textContent=i.get("wcf.global.button.disabledI18n"),b.appendChild(v),b.addEventListener(WCF_CLICK_EVENT,w),_.appendChild(b));var C=null;if(!0===u||a.size)for(var E=0,L=_.childElementCount;E<L;E++)if(~~elData(_.children[E],"language-id")===LANGUAGE_ID){C=_.children[E];break}s.init(g),s.registerCallback(f.id,h),l.set(e,{buttonLabel:g.children[0],callbacks:new t,element:n,languageId:0,isEnabled:!0,forceSelection:u});var I=r.parentByTag(n,"FORM");if(null!==I){I.addEventListener("submit",p);var S=d.get(I);void 0===S&&(S=[],d.set(I,S)),S.push(e)}null!==C&&w({currentTarget:C},!0)},_select:function(e,i,n){for(var a,r=l.get(e),o=s.getDropdownMenu(r.element.closest(".inputAddon").id),c="",d=0,h=o.childElementCount;d<h;d++){a=o.children[d];var p=elData(a,"language-id");p.length&&i===~~p&&(c=a.children[0].textContent)}if(r.languageId!==i){var f=u.get(e);r.languageId&&f.set(r.languageId,r.element.value),0===i?u.set(e,new t):(r.buttonLabel.classList.contains("active")||!0===n)&&(r.element.value=f.has(i)?f.get(i):""),r.buttonLabel.textContent=c,r.buttonLabel.classList[i?"add":"remove"]("active"),r.languageId=i}n||(r.element.blur(),r.element.focus()),r.callbacks.has("select")&&r.callbacks.get("select")(r.element)},_dropdownToggle:function(e,t){if("open"===t)for(var i,n,a=s.getDropdownMenu(e),r=elData(elById(e),"input-id"),o=l.get(r),c=u.get(r),d=0,h=a.childElementCount;d<h;d++)if(i=a.children[d],n=~~elData(i,"language-id")){var p=!1;o.languageId&&(p=n===o.languageId?""===o.element.value.trim():!c.get(n)),i.classList[p?"add":"remove"]("missingValue")}},_submit:function(e){for(var t,i,n,a,r=d.get(e.currentTarget),o=0,s=r.length;o<s;o++)i=r[o],t=l.get(i),t.isEnabled&&(a=u.get(i),t.callbacks.has("submit")&&t.callbacks.get("submit")(t.element),t.languageId&&a.set(t.languageId,t.element.value),a.size&&(a.forEach(function(t,a){n=elCreate("input"),n.type="hidden",n.name=i+"_i18n["+a+"]",n.value=t,e.currentTarget.appendChild(n)}),t.element.removeAttribute("name")))},getValues:function(e){var t=l.get(e);if(void 0===t)throw new Error("Expected a valid i18n input element, '"+e+"' is not i18n input field.");var i=u.get(e);return i.set(t.languageId,t.element.value),i},setValues:function(i,n){var a=l.get(i);if(void 0===a)throw new Error("Expected a valid i18n input element, '"+i+"' is not i18n input field.");if(e.isPlainObject(n)&&(n=t.fromObject(n)),a.element.value="",n.has(0))return a.element.value=n.get(0),n.delete(0),u.set(i,n),void this._select(i,0,!0);u.set(i,n),a.languageId=0,this._select(i,LANGUAGE_ID,!0)},disable:function(e){var t=l.get(e);if(void 0===t)throw new Error("Expected a valid element, '"+e+"' is not an i18n input field.");if(t.isEnabled){t.isEnabled=!1,elHide(t.buttonLabel.parentNode);var i=t.buttonLabel.parentNode.parentNode;i.classList.remove("inputAddon"),i.classList.remove("dropdown")}},enable:function(e){var t=l.get(e);if(void 0===t)throw new Error("Expected a valid i18n input element, '"+e+"' is not i18n input field.");if(!t.isEnabled){t.isEnabled=!0,elShow(t.buttonLabel.parentNode);var i=t.buttonLabel.parentNode.parentNode;i.classList.add("inputAddon"),i.classList.add("dropdown")}},isEnabled:function(e){var t=l.get(e);if(void 0===t)throw new Error("Expected a valid i18n input element, '"+e+"' is not i18n input field.");return t.isEnabled},validate:function(e,t){var i=l.get(e);if(void 0===i)throw new Error("Expected a valid i18n input element, '"+e+"' is not i18n input field.");if(!i.isEnabled)return!0;var n=u.get(e),a=s.getDropdownMenu(i.element.parentNode.id);i.languageId&&n.set(i.languageId,i.element.value);for(var r,o,c=!1,d=!1,h=0,p=a.childElementCount;h<p;h++)if(r=a.children[h],o=~~elData(r,"language-id"))if(n.has(o)&&0!==n.get(o).length){if(c)return!1;d=!0}else{if(d)return!1;c=!0}return!c||t}}}),define("WoltLabSuite/Core/Language/Text",["Core","./Input"],function(e,t){"use strict";return{init:function(e,i,n,a){var r=elById(e);if(!r||"TEXTAREA"!==r.nodeName||!r.classList.contains("wysiwygTextarea"))throw new Error('Expected <textarea class="wysiwygTextarea" /> for id \''+e+"'.");t.init(e,i,n,a),t.registerCallback(e,"select",this._callbackSelect.bind(this)),t.registerCallback(e,"submit",this._callbackSubmit.bind(this))},_callbackSelect:function(e){void 0!==window.jQuery&&window.jQuery(e).redactor("code.set",e.value)},_callbackSubmit:function(e){void 0!==window.jQuery&&(e.value=window.jQuery(e).redactor("code.get"))}}}),define("WoltLabSuite/Core/Media/Editor",["Ajax","Core","Dictionary","Dom/ChangeListener","Dom/Traverse","Language","Ui/Dialog","Ui/Notification","WoltLabSuite/Core/Language/Chooser","WoltLabSuite/Core/Language/Input","EventKey"],function(e,t,i,n,a,r,o,s,l,c,d){"use strict";function u(e){if(this._callbackObject=e||{},this._callbackObject._editorClose&&"function"!=typeof this._callbackObject._editorClose)throw new TypeError("Callback object has no function '_editorClose'.");if(this._callbackObject._editorSuccess&&"function"!=typeof this._callbackObject._editorSuccess)throw new TypeError("Callback object has no function '_editorSuccess'.");this._media=null,this._availableLanguageCount=1,this._categoryIds=[],this._oldCategoryId=0,this._dialogs=new i}return u.prototype={_ajaxSetup:function(){return{data:{actionName:"update",className:"wcf\\data\\media\\MediaAction"}}},_ajaxSuccess:function(e){s.show(),this._callbackObject._editorSuccess&&(this._callbackObject._editorSuccess(this._media,this._oldCategoryId),this._oldCategoryId=0),o.close("mediaEditor_"+this._media.mediaID),this._media=null},_close:function(){this._media=null,this._callbackObject._editorClose&&this._callbackObject._editorClose()},_keyPress:function(e){d.Enter(e)&&(e.preventDefault(),this._saveData())},_saveData:function(){var t=o.getDialog("mediaEditor_"+this._media.mediaID).content,i=elBySel("select[name=categoryID]",t),n=elBySel("input[name=altText]",t),s=elBySel("textarea[name=caption]",t),d=(elBySel("input[name=captionEnableHtml]",t),elBySel("input[name=title]",t)),u=!1,h=!!n&&a.childByClass(n.parentNode.parentNode,"innerError"),p=!!s&&a.childByClass(s.parentNode.parentNode,"innerError"),f=a.childByClass(d.parentNode.parentNode,"innerError");if(this._oldCategoryId=this._media.categoryID,this._categoryIds.length&&(this._media.categoryID=~~i.value,-1===this._categoryIds.indexOf(this._media.categoryID)&&(this._media.categoryID=0)),this._availableLanguageCount>1?(this._media.isMultilingual=~~elBySel("input[name=isMultilingual]",t).checked,this._media.languageID=this._media.isMultilingual?null:l.getLanguageId("mediaEditor_"+this._media.mediaID+"_languageID")):this._media.languageID=LANGUAGE_ID,this._media.altText={},this._media.caption={},this._media.title={},this._availableLanguageCount>1&&this._media.isMultilingual){if(elById("altText_"+this._media.mediaID)&&!c.validate("altText_"+this._media.mediaID,!0)&&(u=!0,!h)){var m=elCreate("small");m.className="innerError",m.textContent=r.get("wcf.global.form.error.multilingual"),n.parentNode.parentNode.appendChild(m)}if(elById("caption_"+this._media.mediaID)&&!c.validate("caption_"+this._media.mediaID,!0)&&(u=!0,!p)){var m=elCreate("small");m.className="innerError",m.textContent=r.get("wcf.global.form.error.multilingual"),s.parentNode.parentNode.appendChild(m)}if(!c.validate("title_"+this._media.mediaID,!0)&&(u=!0,!f)){var m=elCreate("small");m.className="innerError",m.textContent=r.get("wcf.global.form.error.multilingual"),d.parentNode.parentNode.appendChild(m)}this._media.altText=elById("altText_"+this._media.mediaID)?c.getValues("altText_"+this._media.mediaID).toObject():"",this._media.caption=elById("caption_"+this._media.mediaID)?c.getValues("caption_"+this._media.mediaID).toObject():"",this._media.title=c.getValues("title_"+this._media.mediaID).toObject()}else this._media.altText[this._media.languageID]=n?n.value:"",this._media.caption[this._media.languageID]=s?s.value:"",this._media.title[this._media.languageID]=d.value;this._media.captionEnableHtml=~~elBySel("input[name=captionEnableHtml]",t).checked;for(var g={allowAll:~~elById("mediaEditor_"+this._media.mediaID+"_aclAllowAll").checked,group:[],user:[]},v=elBySelAll('input[name="aclValues[group][]"]',t),_=0,b=v.length;_<b;_++)g.group.push(~~v[_].value);for(var w=elBySelAll('input[name="aclValues[user][]"]',t),_=0,b=w.length;_<b;_++)g.user.push(~~w[_].value);u||(h&&elRemove(h),p&&elRemove(p),f&&elRemove(f),e.api(this,{actionName:"update",objectIDs:[this._media.mediaID],parameters:{aclValues:g,altText:this._media.altText,caption:this._media.caption,data:{captionEnableHtml:this._media.captionEnableHtml,categoryID:this._media.categoryID,isMultilingual:this._media.isMultilingual,languageID:this._media.languageID},title:this._media.title}}))},_updateLanguageFields:function(e,t){e&&(t=e.currentTarget);var i=elById("mediaEditor_"+this._media.mediaID+"_languageIDContainer").parentNode;t.checked?(c.enable("title_"+this._media.mediaID),elById("caption_"+this._media.mediaID)&&c.enable("caption_"+this._media.mediaID),elById("altText_"+this._media.mediaID)&&c.enable("altText_"+this._media.mediaID),elHide(i)):(c.disable("title_"+this._media.mediaID),elById("caption_"+this._media.mediaID)&&c.disable("caption_"+this._media.mediaID),elById("altText_"+this._media.mediaID)&&c.disable("altText_"+this._media.mediaID),elShow(i))},edit:function(e){if("object"!=typeof e&&(e={mediaID:~~e}),null!==this._media)throw new Error("Cannot edit media with id '"+e.mediaID+"' while editing media with id '"+this._media.mediaID+"'");this._media=e,this._dialogs.has("mediaEditor_"+e.mediaID)||this._dialogs.set("mediaEditor_"+e.mediaID,{_dialogSetup:function(){return{id:"mediaEditor_"+e.mediaID,options:{backdropCloseOnClick:!1,onClose:this._close.bind(this),title:r.get("wcf.media.edit")},source:{after:function(e,t){this._availableLanguageCount=~~t.returnValues.availableLanguageCount,this._categoryIds=t.returnValues.categoryIDs.map(function(e){return~~e});t.returnValues.mediaData&&(this._media=t.returnValues.mediaData),setTimeout(function(){this._availableLanguageCount>1&&l.setLanguageId("mediaEditor_"+this._media.mediaID+"_languageID",this._media.languageID||LANGUAGE_ID),this._categoryIds.length&&(elBySel("select[name=categoryID]",e).value=~~this._media.categoryID);var t=elBySel("input[name=title]",e),a=elBySel("input[name=altText]",e),r=elBySel("textarea[name=caption]",e);if(this._availableLanguageCount>1&&this._media.isMultilingual?(elById("altText_"+this._media.mediaID)&&c.setValues("altText_"+this._media.mediaID,i.fromObject(this._media.altText||{})),elById("caption_"+this._media.mediaID)&&c.setValues("caption_"+this._media.mediaID,i.fromObject(this._media.caption||{})),c.setValues("title_"+this._media.mediaID,i.fromObject(this._media.title||{}))):(t.value=this._media.title?this._media.title[this._media.languageID||LANGUAGE_ID]:"",a&&(a.value=this._media.altText?this._media.altText[this._media.languageID||LANGUAGE_ID]:""),r&&(r.value=this._media.caption?this._media.caption[this._media.languageID||LANGUAGE_ID]:"")),this._availableLanguageCount>1){var o=elBySel("input[name=isMultilingual]",e);o.addEventListener("change",this._updateLanguageFields.bind(this)),this._updateLanguageFields(null,o)}var s=this._keyPress.bind(this);a&&a.addEventListener("keypress",s),t.addEventListener("keypress",s),elBySel("button[data-type=submit]",e).addEventListener(WCF_CLICK_EVENT,this._saveData.bind(this)),document.activeElement.blur(),elById("mediaEditor_"+this._media.mediaID).parentNode.scrollTop=0,n.trigger()}.bind(this),200)}.bind(this),data:{actionName:"getEditorDialog",className:"wcf\\data\\media\\MediaAction",objectIDs:[e.mediaID]}}}}.bind(this)}),o.open(this._dialogs.get("mediaEditor_"+e.mediaID))}},u}),define("WoltLabSuite/Core/Media/Upload",["Core","DateUtil","Dom/ChangeListener","Dom/Traverse","Dom/Util","EventHandler","Language","Permission","Upload","User","WoltLabSuite/Core/FileUtil"],function(e,t,i,n,a,r,o,s,l,c,d){"use strict";function u(t,i,n){n=n||{},this._mediaManager=null,n.mediaManager&&(this._mediaManager=n.mediaManager,delete n.mediaManager),this._categoryId=null,l.call(this,t,i,e.extend({className:"wcf\\data\\media\\MediaAction",multiple:!!this._mediaManager,singleFileRequests:!0},n))}return e.inherit(u,l,{_createFileElement:function(e){var n;if("OL"===this._target.nodeName||"UL"===this._target.nodeName)n=elCreate("li");else{if("TBODY"===this._target.nodeName){var r=elByTag("TR",this._target)[0],s=this._target.parentNode.parentNode;"none"===s.style.getPropertyValue("display")?(n=r,s.style.removeProperty("display"),elRemove(elById(elData(this._target,"no-items-info")))):(n=r.cloneNode(!0),n.removeAttribute("id"),a.identify(n));for(var l,u=elByTag("TD",n),h=0,p=u.length;h<p;h++)if(l=u[h],l.classList.contains("columnMark"))elBySelAll("[data-object-id]",l,elHide);else if(l.classList.contains("columnIcon"))elBySelAll("[data-object-id]",l,elHide),elByClass("mediaEditButton",l)[0].classList.add("jsMediaEditButton"),elData(elByClass("jsDeleteButton",l)[0],"confirm-message-html",o.get("wcf.media.delete.confirmMessage",{title:e.name}));else if(l.classList.contains("columnFilename")){var f=elByTag("IMG",l);f.length||(f=elByClass("icon48",l));var m=elCreate("span");m.className="icon icon48 fa-spinner mediaThumbnail",a.replaceElement(f[0],m);var g=elBySelAll(".box48 > div > p",l);g[0].textContent=e.name;var v=elByTag("A",g[1])[0];v||(v=elCreate("a"),elByTag("SMALL",g[1])[0].appendChild(v)),v.setAttribute("href",c.getLink()),v.textContent=c.username}else l.classList.contains("columnUploadTime")?(l.innerHTML="",l.appendChild(t.getTimeElement(new Date))):l.classList.contains("columnDigits")?l.textContent=d.formatFilesize(e.size):l.innerHTML="";return a.prepend(n,this._target),n}n=elCreate("p")}var _=elCreate("div");_.className="mediaThumbnail",n.appendChild(_);var b=elCreate("span");b.className="icon icon144 fa-spinner",_.appendChild(b);var w=elCreate("div");w.className="mediaInformation",n.appendChild(w);var y=elCreate("p");y.className="mediaTitle",y.textContent=e.name,w.appendChild(y);var C=elCreate("progress");return elAttr(C,"max",100),w.appendChild(C),a.prepend(n,this._target),i.trigger(),n},_getParameters:function(){if(this._mediaManager){var t={imagesOnly:this._mediaManager.getOption("imagesOnly")},i=this._mediaManager.getCategoryId();return i&&(t.categoryID=i),e.extend(u._super.prototype._getParameters.call(this),t)}return u._super.prototype._getParameters.call(this)},_replaceFileIcon:function(e,t,i){if(t.tinyThumbnailType){var n=elCreate("img");elAttr(n,"src",t.tinyThumbnailLink),elAttr(n,"alt",""),n.style.setProperty("width",i+"px"),n.style.setProperty("height",i+"px"),a.replaceElement(e,n)}else{e.classList.remove("fa-spinner");var r=d.getIconNameByFilename(t.filename);r&&(r="-"+r),e.classList.add("fa-file"+r+"-o")}},_success:function(e,t){for(var a=this._fileElements[e],s=0,l=a.length;s<l;s++){var c=a[s],d=elData(c,"internal-file-id"),u=t.returnValues.media[d];if("TR"===c.tagName)if(u){for(var h=elBySelAll("[data-object-id]",c),s=0,l=h.length;s<l;s++)elData(h[s],"object-id",~~u.mediaID),elShow(h[s]);elByClass("columnMediaID",c)[0].textContent=u.mediaID;var p=elByClass("fa-spinner",c)[0];this._replaceFileIcon(p,u,48)}else{var f=t.returnValues.errors[d];f||(f={errorType:"uploadFailed",filename:elData(c,"filename")});var p=elByClass("fa-spinner",c)[0];p.classList.remove("fa-spinner"),p.classList.add("fa-remove"),p.classList.add("pointer"),p.classList.add("jsTooltip"),elAttr(p,"title",o.get("wcf.global.button.delete")),p.addEventListener(WCF_CLICK_EVENT,function(e){elRemove(e.currentTarget.parentNode.parentNode.parentNode),r.fire("com.woltlab.wcf.media.upload","removedErroneousUploadRow")}),c.classList.add("uploadFailed");var m=elBySelAll(".columnFilename .box48 > div > p",c)[1];elInnerError(m,o.get("wcf.media.upload.error."+f.errorType,{filename:f.filename})),elRemove(m)}else if(elRemove(n.childByTag(n.childByClass(c,"mediaInformation"),"PROGRESS")),u){var p=n.childByTag(n.childByClass(c,"mediaThumbnail"),"SPAN");this._replaceFileIcon(p,u,144),c.className="jsClipboardObject mediaFile",elData(c,"object-id",u.mediaID),this._mediaManager&&(this._mediaManager.setupMediaElement(u,c),this._mediaManager.addMedia(u,c))}else{var f=t.returnValues.errors[d];f||(f={errorType:"uploadFailed",filename:elData(c,"filename")});var p=n.childByTag(n.childByClass(c,"mediaThumbnail"),"SPAN");p.classList.remove("fa-spinner"),p.classList.add("fa-remove"),p.classList.add("pointer"),c.classList.add("uploadFailed"),c.classList.add("jsTooltip"),elAttr(c,"title",o.get("wcf.global.button.delete")),c.addEventListener(WCF_CLICK_EVENT,function(){elRemove(this)});var g=n.childByClass(n.childByClass(c,"mediaInformation"),"mediaTitle");g.innerText=o.get("wcf.media.upload.error."+f.errorType,{filename:f.filename})}i.trigger()}r.fire("com.woltlab.wcf.media.upload","success",{files:a,isMultiFileUpload:-1!==this._multiFileUploadIds.indexOf(e),media:t.returnValues.media,upload:this,uploadId:e})},_uploadFiles:function(e,t){return u._super.prototype._uploadFiles.call(this,e,t)}}),u}),define("WoltLabSuite/Core/Media/List/Upload",["Core","Dom/Util","../Upload"],function(e,t,i){"use strict";function n(e,t,n){i.call(this,e,t,n)}return e.inherit(n,i,{_createButton:function(){n._super.prototype._createButton.call(this);var e=elBySel("span",this._button),i=document.createTextNode(" ");t.prepend(i,e);var a=elCreate("span");a.className="icon icon16 fa-upload",t.prepend(a,e)},_getParameters:function(){return this._options.categoryId?e.extend(n._super.prototype._getParameters.call(this),{categoryID:this._options.categoryId}):n._super.prototype._getParameters.call(this)}}),n}),define("WoltLabSuite/Core/Media/Clipboard",["Ajax","Dom/ChangeListener","EventHandler","Language","Ui/Dialog","Ui/Notification","WoltLabSuite/Core/Controller/Clipboard","WoltLabSuite/Core/Media/Editor","WoltLabSuite/Core/Media/List/Upload"],function(e,t,i,n,a,r,o,s,l){"use strict";var c,d=[];return{init:function(e,t,n){o.setup({hasMarkedItems:t,pageClassName:e}),c=n,i.add("com.woltlab.wcf.clipboard","com.woltlab.wcf.media",this._clipboardAction.bind(this))},_ajaxSetup:function(){return{data:{className:"wcf\\data\\media\\MediaAction"}}},_ajaxSuccess:function(e){switch(e.actionName){case"getSetCategoryDialog":a.open(this,e.returnValues.template);break;case"setCategory":a.close(this),r.show(),o.reload()}},_dialogSetup:function(){return{id:"mediaSetCategoryDialog",options:{onSetup:function(e){elBySel("button",e).addEventListener(WCF_CLICK_EVENT,function(t){t.preventDefault(),this._setCategory(~~elBySel('select[name="categoryID"]',e).value),t.currentTarget.disabled=!0}.bind(this))}.bind(this),title:n.get("wcf.media.setCategory")},source:null}},_clipboardAction:function(t){var i=t.data.parameters.objectIDs;switch(t.data.actionName){case"com.woltlab.wcf.media.delete":null!==t.responseData&&c.clipboardDeleteMedia(i);break;case"com.woltlab.wcf.media.insert":c.clipboardInsertMedia(i);break;case"com.woltlab.wcf.media.setCategory":d=i,e.api(this,{actionName:"getSetCategoryDialog"})}},_setCategory:function(t){e.api(this,{actionName:"setCategory",objectIDs:d,parameters:{categoryID:t}})}}}),define("WoltLabSuite/Core/Notification/Handler",["Ajax","Core","EventHandler","StringUtil"],function(e,t,i,n){"use strict";if(!("Promise"in window&&"Notification"in window))return{setup:function(){}};var a=!1,r="",o=0,s=window.TIME_NOW,l=null,c=0;return{setup:function(e){if(e=t.extend({enableNotifications:!1,icon:"",sessionKeepAlive:0},e),r=e.icon,c=60*e.sessionKeepAlive,this._prepareNextRequest(),document.addEventListener("visibilitychange",this._onVisibilityChange.bind(this)),window.addEventListener("storage",this._onStorage.bind(this)),this._onVisibilityChange(null),e.enableNotifications)switch(window.Notification.permission){case"granted":a=!0;break;case"default":window.Notification.requestPermission(function(e){"granted"===e&&(a=!0)})}},_onVisibilityChange:function(e){if(null!==e&&!document.hidden){(Date.now()-o)/6e4>4&&(this._resetTimer(),this._dispatchRequest())}o=document.hidden?Date.now():0},_getNextDelay:function(){if(0===o)return 5;var e=~~((Date.now()-o)/6e4);return e<15?5:e<30?10:15},_resetTimer:function(){null!==l&&(window.clearTimeout(l),l=null)},_prepareNextRequest:function(){this._resetTimer();var e=Math.min(this._getNextDelay(),c);l=window.setTimeout(this._dispatchRequest.bind(this),6e4*e)},_dispatchRequest:function(){var t={};i.fire("com.woltlab.wcf.notification","beforePoll",t),t.lastRequestTimestamp=s,e.api(this,{parameters:t})},_onStorage:function(){this._prepareNextRequest();var e,n,a=!1;try{e=window.localStorage.getItem(t.getStoragePrefix()+"notification"),n=window.localStorage.getItem(t.getStoragePrefix()+"keepAliveData"),e=JSON.parse(e),n=JSON.parse(n)}catch(e){a=!0}a||i.fire("com.woltlab.wcf.notification","onStorage",{pollData:e,keepAliveData:n})},_ajaxSuccess:function(e){var n=!1,a=e.returnValues.keepAliveData,r=e.returnValues.pollData;window.WCF.System.PushNotification.executeCallbacks(a);try{window.localStorage.setItem(t.getStoragePrefix()+"notification",JSON.stringify(r)),window.localStorage.setItem(t.getStoragePrefix()+"keepAliveData",JSON.stringify(a))}catch(e){n=!0,window.console.log(e)}n||this._prepareNextRequest(),s=e.returnValues.lastRequestTimestamp,i.fire("com.woltlab.wcf.notification","afterPoll",r),this._showNotification(r)},_showNotification:function(e){if(a&&"object"==typeof e.notification&&"string"==typeof e.notification.message){var t=new window.Notification(e.notification.title,{body:n.unescapeHTML(e.notification.message),icon:r});t.onclick=function(){window.focus(),t.close(),window.location=e.notification.link}}},_ajaxSetup:function(){return{data:{actionName:"poll",className:"wcf\\data\\session\\SessionAction"},ignoreError:!window.ENABLE_DEBUG_MODE,silent:!window.ENABLE_DEBUG_MODE}}}}),define("WoltLabSuite/Core/Ui/Redactor/DragAndDrop",["Dictionary","EventHandler","Language"],function(e,t,i){"use strict";var n=!1,a=new e,r=!1,o=!1,s=null;return{init:function(e){n||this._setup(),a.set(e.uuid,{editor:e,element:null})},_dragOver:function(e){if(e.preventDefault(),e.dataTransfer&&e.dataTransfer.types){var t=!1;for(var n in e.dataTransfer)if(e.dataTransfer.hasOwnProperty(n)&&n.match(/^moz/)){t=!0;break}if(o=!1,t)"application/x-moz-file"===e.dataTransfer.types[0]&&(o=!0);else for(var s=0;s<e.dataTransfer.types.length;s++)if("Files"===e.dataTransfer.types[s]){o=!0;break}o&&(r||(r=!0,a.forEach(function(e,t){var n=e.editor.$editor[0];if(!n.parentNode)return void a.delete(t);var r=e.element;null===r&&(r=elCreate("div"),r.className="redactorDropArea",elData(r,"element-id",e.editor.$element[0].id),elData(r,"drop-here",i.get("wcf.attachment.dragAndDrop.dropHere")),elData(r,"drop-now",i.get("wcf.attachment.dragAndDrop.dropNow")),r.addEventListener("dragover",function(){r.classList.add("active")}),r.addEventListener("dragleave",function(){r.classList.remove("active")}),r.addEventListener("drop",this._drop.bind(this)),e.element=r),n.parentNode.insertBefore(r,n),r.style.setProperty("top",n.offsetTop+"px","")}.bind(this))))}},_drop:function(e){if(o&&e.dataTransfer&&e.dataTransfer.files.length){e.preventDefault();for(var i=elData(e.currentTarget,"element-id"),n=0,a=e.dataTransfer.files.length;n<a;n++)t.fire("com.woltlab.wcf.redactor2","dragAndDrop_"+i,{file:e.dataTransfer.files[n]});this._dragLeave()}},_dragLeave:function(){r&&o&&(null!==s&&window.clearTimeout(s),s=window.setTimeout(function(){r||a.forEach(function(e){e.element&&e.element.parentNode&&(e.element.classList.remove("active"),elRemove(e.element))}),s=null},100),r=!1)},_globalDrop:function(e){if(null===e.target.closest(".redactor-layer")){var i={cancelDrop:!0,event:e};a.forEach(function(e){t.fire("com.woltlab.wcf.redactor2","dragAndDrop_globalDrop_"+e.editor.$element[0].id,i)}),i.cancelDrop&&e.preventDefault()}this._dragLeave(e)},_setup:function(){window.addEventListener("dragend",function(e){e.preventDefault()}),window.addEventListener("dragover",this._dragOver.bind(this)),window.addEventListener("dragleave",this._dragLeave.bind(this)),window.addEventListener("drop",this._globalDrop.bind(this)),n=!0}}}),define("WoltLabSuite/Core/Ui/DragAndDrop",["Core","EventHandler","WoltLabSuite/Core/Ui/Redactor/DragAndDrop"],function(e,t,i){return{register:function(n){var a=e.getUuid();n=e.extend({element:"",elementId:"",onDrop:function(e){},onGlobalDrop:function(e){}}),t.add("com.woltlab.wcf.redactor2","dragAndDrop_"+n.elementId,n.onDrop),t.add("com.woltlab.wcf.redactor2","dragAndDrop_globalDrop_"+n.elementId,n.onGlobalDrop),i.init({uuid:a,$editor:[n.element],$element:[{id:n.elementId}]})}}}),define("WoltLabSuite/Core/Ui/Suggestion",["Ajax","Core","Ui/SimpleDropdown"],function(e,t,i){"use strict";function n(e,t){this.init(e,t)}return n.prototype={init:function(e,i){if(this._dropdownMenu=null,this._value="",this._element=elById(e),null===this._element)throw new Error("Expected a valid element id.");if(this._options=t.extend({ajax:{actionName:"getSearchResultList",className:"",interfaceName:"wcf\\data\\ISearchAction",parameters:{data:{}}},callbackSelect:null,excludedSearchValues:[],threshold:3},i),"function"!=typeof this._options.callbackSelect)throw new Error("Expected a valid callback for option 'callbackSelect'.");this._element.addEventListener(WCF_CLICK_EVENT,function(e){e.stopPropagation()}),this._element.addEventListener("keydown",this._keyDown.bind(this)),this._element.addEventListener("keyup",this._keyUp.bind(this))},addExcludedValue:function(e){-1===this._options.excludedSearchValues.indexOf(e)&&this._options.excludedSearchValues.push(e)},removeExcludedValue:function(e){var t=this._options.excludedSearchValues.indexOf(e);-1!==t&&this._options.excludedSearchValues.splice(t,1)},isActive:function(){return null!==this._dropdownMenu&&i.isOpen(this._element.id)},_keyDown:function(e){if(!this.isActive())return!0;if(13!==e.keyCode&&27!==e.keyCode&&38!==e.keyCode&&40!==e.keyCode)return!0;for(var t,n=0,a=this._dropdownMenu.childElementCount;n<a&&(t=this._dropdownMenu.children[n],!t.classList.contains("active"));)n++;if(13===e.keyCode)i.close(this._element.id),this._select(t);else if(27===e.keyCode){if(!i.isOpen(this._element.id))return!0;i.close(this._element.id)}else{var r=0;38===e.keyCode?r=(0===n?a:n)-1:40===e.keyCode&&(r=n+1)===a&&(r=0),r!==n&&(t.classList.remove("active"),this._dropdownMenu.children[r].classList.add("active"))}return e.preventDefault(),!1},_select:function(e){var t=e instanceof Event;t&&(e=e.currentTarget.parentNode);var i=e.children[0];this._options.callbackSelect(this._element.id,{objectId:elData(i,"object-id"),value:e.textContent,type:elData(i,"type")}),t&&this._element.focus()},_keyUp:function(t){var n=t.currentTarget.value.trim();if(this._value!==n){if(n.length<this._options.threshold)return null!==this._dropdownMenu&&i.close(this._element.id),void(this._value=n);this._value=n,e.api(this,{parameters:{data:{excludedSearchValues:this._options.excludedSearchValues,searchString:n}}})}},_ajaxSetup:function(){return{data:this._options.ajax}},_ajaxSuccess:function(e){if(null===this._dropdownMenu?(this._dropdownMenu=elCreate("div"),this._dropdownMenu.className="dropdownMenu",i.initFragment(this._element,this._dropdownMenu)):this._dropdownMenu.innerHTML="",e.returnValues.length){for(var t,n,a,r=0,o=e.returnValues.length;r<o;r++)n=e.returnValues[r],t=elCreate("a"),n.icon?(t.className="box16",t.innerHTML=n.icon+" <span></span>",t.children[1].textContent=n.label):t.textContent=n.label,elData(t,"object-id",n.objectID),n.type&&elData(t,"type",n.type),t.addEventListener(WCF_CLICK_EVENT,this._select.bind(this)),a=elCreate("li"),0===r&&(a.className="active"),a.appendChild(t),this._dropdownMenu.appendChild(a);i.open(this._element.id,!0)}else i.close(this._element.id)}},n}),define("WoltLabSuite/Core/Ui/ItemList",["Core","Dictionary","Language","Dom/Traverse","EventKey","WoltLabSuite/Core/Ui/Suggestion","Ui/SimpleDropdown"],function(e,t,i,n,a,r,o){"use strict";var s="",l=new t,c=!1,d=null,u=null,h=null,p=null,f=null,m=null;return{init:function(t,i,a){var s=elById(t);if(null===s)throw new Error("Expected a valid element id, '"+t+"' is invalid.");if(l.has(t)){var c=l.get(t);for(var d in c)if(c.hasOwnProperty(d)){var u=c[d];u instanceof Element&&u.parentNode&&elRemove(u)}o.destroy(t),l.delete(t)}a=e.extend({ajax:{actionName:"getSearchResultList",className:"",data:{}},excludedSearchValues:[],maxItems:-1,maxLength:-1,restricted:!1,isCSV:!1,callbackChange:null,callbackSubmit:null,callbackSyncShadow:null,callbackSetupValues:null,submitFieldName:""},a);var h=n.parentByTag(s,"FORM");if(null!==h&&!1===a.isCSV){if(!a.submitFieldName.length&&"function"!=typeof a.callbackSubmit)throw new Error("Expected a valid function for option 'callbackSubmit', a non-empty value for option 'submitFieldName' or enabling the option 'submitFieldCSV'.");h.addEventListener("submit",function(){var e=this.getValues(t);if(a.submitFieldName.length)for(var i,n=0,r=e.length;n<r;n++)i=elCreate("input"),i.type="hidden",i.name=a.submitFieldName.replace(/{$objectId}/,e[n].objectId),i.value=e[n].value,h.appendChild(i);else a.callbackSubmit(h,e)}.bind(this))}this._setup();var p=this._createUI(s,a),f=new r(t,{ajax:a.ajax,callbackSelect:this._addItem.bind(this),excludedSearchValues:a.excludedSearchValues});if(l.set(t,{dropdownMenu:null,element:p.element,list:p.list,listItem:p.element.parentNode,options:a,shadow:p.shadow,suggestion:f}),i=a.callbackSetupValues?a.callbackSetupValues():p.values.length?p.values:i,Array.isArray(i))for(var m,g=0,v=i.length;g<v;g++)m=i[g],"string"==typeof m&&(m={objectId:0,value:m}),this._addItem(t,m)},getValues:function(e){if(!l.has(e))throw new Error("Element id '"+e+"' is unknown.");var t=l.get(e),i=[];return elBySelAll(".item > span",t.list,function(e){i.push({objectId:~~elData(e,"object-id"),value:e.textContent,type:elData(e,"type")})}),i},setValues:function(e,t){if(!l.has(e))throw new Error("Element id '"+e+"' is unknown.");var i,a,r=l.get(e),o=n.childrenByClass(r.list,"item");for(i=0,a=o.length;i<a;i++)this._removeItem(null,o[i],!0);for(i=0,a=t.length;i<a;i++)this._addItem(e,t[i])},_setup:function(){c||(c=!0,d=this._keyDown.bind(this),u=this._keyPress.bind(this),h=this._keyUp.bind(this),p=this._paste.bind(this),f=this._removeItem.bind(this),m=this._blur.bind(this))},_createUI:function(e,t){var i=elCreate("ol");i.className="inputItemList"+(e.disabled?" disabled":""),elData(i,"element-id",e.id),i.addEventListener(WCF_CLICK_EVENT,function(t){
+t.target===i&&e.focus()});var n=elCreate("li");n.className="input",i.appendChild(n),e.addEventListener("keydown",d),e.addEventListener("keypress",u),e.addEventListener("keyup",h),e.addEventListener("paste",p);var a=e===document.activeElement;a&&e.blur(),e.addEventListener("blur",m),e.parentNode.insertBefore(i,e),n.appendChild(e),a&&window.setTimeout(function(){e.focus()},1),-1!==t.maxLength&&elAttr(e,"maxLength",t.maxLength);var r=null,o=[];if(t.isCSV){r=elCreate("input"),r.className="itemListInputShadow",r.type="hidden",r.name=e.name,e.removeAttribute("name"),i.parentNode.insertBefore(r,i);for(var s,l=e.value.split(","),c=0,f=l.length;c<f;c++)s=l[c].trim(),s.length&&o.push(s);if("TEXTAREA"===e.nodeName){var g=elCreate("input");g.type="text",e.parentNode.insertBefore(g,e),g.id=e.id,elRemove(e),e=g}}return{element:e,list:i,shadow:r,values:o}},_acceptsNewItems:function(e){var t=l.get(e);return-1===t.options.maxItems||t.list.childElementCount-1<t.options.maxItems},_handleLimit:function(e){var t=l.get(e);this._acceptsNewItems(e)?t.element.disabled&&(t.element.disabled=!1,t.element.removeAttribute("placeholder")):t.element.disabled||(t.element.disabled=!0,elAttr(t.element,"placeholder",i.get("wcf.global.form.input.maxItems")))},_keyDown:function(e){var t=e.currentTarget,i=t.parentNode.previousElementSibling;s=t.id,8===e.keyCode?0===t.value.length&&null!==i&&(i.classList.contains("active")?this._removeItem(null,i):i.classList.add("active")):27===e.keyCode&&null!==i&&i.classList.contains("active")&&i.classList.remove("active")},_keyPress:function(e){if(a.Enter(e)||a.Comma(e)){if(e.preventDefault(),l.get(e.currentTarget.id).options.restricted)return;var t=e.currentTarget.value.trim();t.length&&this._addItem(e.currentTarget.id,{objectId:0,value:t})}},_paste:function(e){var t="";t="object"==typeof window.clipboardData?window.clipboardData.getData("Text"):e.clipboardData.getData("text/plain");var i=e.currentTarget,n=i.id,a=~~elAttr(i,"maxLength");t.split(/,/).forEach(function(e){e=e.trim(),a&&e.length>a&&(e=e.substr(0,a)),e.length>0&&this._acceptsNewItems(n)&&this._addItem(n,{objectId:0,value:e})}.bind(this)),e.preventDefault()},_keyUp:function(e){var t=e.currentTarget;if(t.value.length>0){var i=t.parentNode.previousElementSibling;null!==i&&i.classList.remove("active")}},_addItem:function(e,t){var i=l.get(e),n=elCreate("li");n.className="item";var a=elCreate("span");if(a.className="content",elData(a,"object-id",t.objectId),t.type&&elData(a,"type",t.type),a.textContent=t.value,n.appendChild(a),!i.element.disabled){var r=elCreate("a");r.className="icon icon16 fa-times",r.addEventListener(WCF_CLICK_EVENT,f),n.appendChild(r)}i.list.insertBefore(n,i.listItem),i.suggestion.addExcludedValue(t.value),i.element.value="",i.element.disabled||this._handleLimit(e);var o=this._syncShadow(i);"function"==typeof i.options.callbackChange&&(null===o&&(o=this.getValues(e)),i.options.callbackChange(e,o))},_removeItem:function(e,t,i){t=null===e?t:e.currentTarget.parentNode;var n=t.parentNode,a=elData(n,"element-id"),r=l.get(a);r.suggestion.removeExcludedValue(t.children[0].textContent),n.removeChild(t),i||r.element.focus(),this._handleLimit(a);var o=this._syncShadow(r);"function"==typeof r.options.callbackChange&&(null===o&&(o=this.getValues(a)),r.options.callbackChange(a,o))},_syncShadow:function(e){if(!e.options.isCSV)return null;if("function"==typeof e.options.callbackSyncShadow)return e.options.callbackSyncShadow(e);for(var t="",i=this.getValues(e.element.id),n=0,a=i.length;n<a;n++)t+=(t.length?",":"")+i[n].value;return e.shadow.value=t,i},_blur:function(e){var t=e.currentTarget,i=l.get(t.id);if(!i.options.restricted){var n=t.value.trim();n.length&&(i.suggestion&&i.suggestion.isActive()||this._addItem(t.id,{objectId:0,value:n}))}}}}),define("WoltLabSuite/Core/Ui/Page/JumpTo",["Language","ObjectMap","Ui/Dialog"],function(e,t,i){"use strict";var n=null,a=null,r=null,o=new t,s=null;return{init:function(e,t){if(null===(t=t||null)){var i=elData(e,"link");t=i?function(e){window.location=i.replace(/pageNo=%d/,"pageNo="+e)}:function(){}}else if("function"!=typeof t)throw new TypeError("Expected a valid function for parameter 'callback'.");o.has(e)||elBySelAll(".jumpTo",e,function(i){i.addEventListener(WCF_CLICK_EVENT,this._click.bind(this,e)),o.set(e,{callback:t})}.bind(this))},_click:function(t,a){n=t,"object"==typeof a&&a.preventDefault(),i.open(this);var o=elData(t,"pages");s.value=o,s.setAttribute("max",o),s.select(),r.textContent=e.get("wcf.page.jumpTo.description").replace(/#pages#/,o)},_keyUp:function(e){if(13===e.which&&!1===a.disabled)return void this._submit();var t=~~s.value;t<1||t>~~elAttr(s,"max")?a.disabled=!0:a.disabled=!1},_submit:function(e){o.get(n).callback(~~s.value),i.close(this)},_dialogSetup:function(){var t='<dl><dt><label for="jsPaginationPageNo">'+e.get("wcf.page.jumpTo")+'</label></dt><dd><input type="number" id="jsPaginationPageNo" value="1" min="1" max="1" class="tiny"><small></small></dd></dl><div class="formSubmit"><button class="buttonPrimary">'+e.get("wcf.global.button.submit")+"</button></div>";return{id:"paginationOverlay",options:{onSetup:function(e){s=elByTag("input",e)[0],s.addEventListener("keyup",this._keyUp.bind(this)),r=elByTag("small",e)[0],a=elByTag("button",e)[0],a.addEventListener(WCF_CLICK_EVENT,this._submit.bind(this))}.bind(this),title:e.get("wcf.global.page.pagination")},source:t}}}}),define("WoltLabSuite/Core/Ui/Pagination",["Core","Language","ObjectMap","StringUtil","WoltLabSuite/Core/Ui/Page/JumpTo"],function(e,t,i,n,a){"use strict";function r(e,t){this.init(e,t)}return r.prototype={SHOW_LINKS:11,init:function(t,i){this._element=t,this._options=e.extend({activePage:1,maxPage:1,callbackShouldSwitch:null,callbackSwitch:null},i),"function"!=typeof this._options.callbackShouldSwitch&&(this._options.callbackShouldSwitch=null),"function"!=typeof this._options.callbackSwitch&&(this._options.callbackSwitch=null),this._element.classList.add("pagination"),this._rebuild(this._element)},_rebuild:function(){var e=!1;this._element.innerHTML="";var i,n=elCreate("ul"),r=elCreate("li");r.className="skip",n.appendChild(r);var o="icon icon24 fa-chevron-left";this._options.activePage>1?(i=elCreate("a"),i.className=o+" jsTooltip",i.href="#",i.title=t.get("wcf.global.page.previous"),i.rel="prev",r.appendChild(i),i.addEventListener(WCF_CLICK_EVENT,this.switchPage.bind(this,this._options.activePage-1))):(r.innerHTML='<span class="'+o+'"></span>',r.classList.add("disabled")),n.appendChild(this._createLink(1));var s=this.SHOW_LINKS-4,l=this._options.activePage-2;l<0&&(l=0);var c=this._options.maxPage-(this._options.activePage+1);c<0&&(c=0),this._options.activePage>1&&this._options.activePage<this._options.maxPage&&s--;var d=s/2,u=this._options.activePage,h=this._options.activePage;u<1&&(u=1),h<1&&(h=1),h>this._options.maxPage-1&&(h=this._options.maxPage-1),l>=d?u-=d:(u-=l,h+=d-l),c>=d?h+=d:(h+=c,u-=d-c),h=Math.ceil(h),u=Math.ceil(u),u<1&&(u=1),h>this._options.maxPage&&(h=this._options.maxPage);var p='<a class="jsTooltip" title="'+t.get("wcf.page.jumpTo")+'">&hellip;</a>';u>1&&(u-1<2?n.appendChild(this._createLink(2)):(r=elCreate("li"),r.className="jumpTo",r.innerHTML=p,n.appendChild(r),e=!0));for(var f=u+1;f<h;f++)n.appendChild(this._createLink(f));h<this._options.maxPage&&(this._options.maxPage-h<2?n.appendChild(this._createLink(this._options.maxPage-1)):(r=elCreate("li"),r.className="jumpTo",r.innerHTML=p,n.appendChild(r),e=!0)),n.appendChild(this._createLink(this._options.maxPage)),r=elCreate("li"),r.className="skip",n.appendChild(r),o="icon icon24 fa-chevron-right",this._options.activePage<this._options.maxPage?(i=elCreate("a"),i.className=o+" jsTooltip",i.href="#",i.title=t.get("wcf.global.page.next"),i.rel="next",r.appendChild(i),i.addEventListener(WCF_CLICK_EVENT,this.switchPage.bind(this,this._options.activePage+1))):(r.innerHTML='<span class="'+o+'"></span>',r.classList.add("disabled")),e&&(elData(n,"pages",this._options.maxPage),a.init(n,this.switchPage.bind(this))),this._element.appendChild(n)},_createLink:function(e){var i=elCreate("li");if(e!==this._options.activePage){var a=elCreate("a");a.textContent=n.addThousandsSeparator(e),a.addEventListener(WCF_CLICK_EVENT,this.switchPage.bind(this,e)),i.appendChild(a)}else i.classList.add("active"),i.innerHTML="<span>"+n.addThousandsSeparator(e)+'</span><span class="invisible">'+t.get("wcf.page.pagePosition",{pageNo:e,pages:this._options.maxPage})+"</span>";return i},getActivePage:function(){return this._options.activePage},getElement:function(){return this._element},getMaxPage:function(){return this._options.maxPage},switchPage:function(t,i){if("object"==typeof i&&(i.preventDefault(),i.currentTarget&&elData(i.currentTarget,"tooltip"))){var n=elById("balloonTooltip");n&&(e.triggerEvent(i.currentTarget,"mouseleave"),n.style.removeProperty("top"),n.style.removeProperty("bottom"))}if((t=~~t)>0&&this._options.activePage!==t&&t<=this._options.maxPage){if(null!==this._options.callbackShouldSwitch&&!0!==this._options.callbackShouldSwitch(t))return;this._options.activePage=t,this._rebuild(),null!==this._options.callbackSwitch&&this._options.callbackSwitch(t)}}},r}),define("WoltLabSuite/Core/Ui/Scroll",["Dom/Util"],function(e){"use strict";var t=null,i=null,n=null,a=null;return{element:function(a,r){if(!(a instanceof Element))throw new TypeError("Expected a valid DOM element.");if(void 0!==r&&"function"!=typeof r)throw new TypeError("Expected a valid callback function.");if(!document.body.contains(a))throw new Error("Element must be part of the visible DOM.");if(null!==t)throw new Error("Cannot scroll to element, a concurrent request is running.");r&&(t=r,null===i&&(i=this._onScroll.bind(this)),window.addEventListener("scroll",i));var o=e.offset(a).top;if(null===n){n=50;var s=elById("pageHeaderPanel");if(null!==s){var l=window.getComputedStyle(s).position;n="fixed"===l||"static"===l?s.offsetHeight:0}}n>0&&(o<=n?o=0:o-=n);var c=window.pageYOffset;window.scrollTo({left:0,top:o,behavior:"smooth"}),window.setTimeout(function(){c===window.pageYOffset&&this._onScroll()}.bind(this),100)},_onScroll:function(){null!==a&&window.clearTimeout(a),a=window.setTimeout(function(){null!==t&&t(),window.removeEventListener("scroll",i),t=null,a=null},100)}}}),define("WoltLabSuite/Core/Controller/Media/List",["Dom/ChangeListener","EventHandler","WoltLabSuite/Core/Controller/Clipboard","WoltLabSuite/Core/Media/Clipboard","WoltLabSuite/Core/Media/Editor","WoltLabSuite/Core/Media/List/Upload"],function(e,t,i,n,a,r){"use strict";var o,s,l=elById("mediaListTableBody");return{init:function(i){i=i||{},s=new r("uploadButton","mediaListTableBody",{categoryId:i.categoryId,multiple:!0}),n.init("wcf\\acp\\page\\MediaListPage",i.hasMarkedItems||!1,this),t.add("com.woltlab.wcf.media.upload","removedErroneousUploadRow",this._deleteCallback.bind(this)),new WCF.Action.Delete("wcf\\data\\media\\MediaAction",".jsMediaRow").setCallback(this._deleteCallback),o=new a({_editorSuccess:function(e,t){e.categoryID!=t&&window.setTimeout(function(){window.location.reload()},500)}}),this._addButtonEventListeners(),e.add("WoltLabSuite/Core/Controller/Media/List",this._addButtonEventListeners.bind(this)),t.add("com.woltlab.wcf.media.upload","success",this._openEditorAfterUpload.bind(this))},_addButtonEventListeners:function(){for(var e,t=elByClass("jsMediaEditButton",l);t.length;)e=t[0],e.classList.remove("jsMediaEditButton"),e.addEventListener(WCF_CLICK_EVENT,this._edit.bind(this))},_deleteCallback:function(e){var t=elByTag("tr",l).length;void 0===e.length?t||window.location.reload():e.length===t?window.location.reload():i.reload.bind(i)},_edit:function(e){o.edit(elData(e.currentTarget,"object-id"))},_openEditorAfterUpload:function(e){if(e.upload===s&&!e.isMultiFileUpload&&!s.hasPendingUploads()){var t=Object.keys(e.media);t.length&&o.edit(this._media.get(~~e.media[t[0]].mediaID))}},clipboardDeleteMedia:function(e){for(var t=elByClass("jsMediaRow"),i=0;i<t.length;i++){var n=t[i],a=~~elData(elByClass("jsClipboardItem",n)[0],"object-id");-1!==e.indexOf(a)&&(elRemove(n),i--)}t.length||window.location.reload()}}}),define("WoltLabSuite/Core/Controller/Notice/Dismiss",["Ajax"],function(e){"use strict";return{setup:function(){var e=elByClass("jsDismissNoticeButton");if(e.length)for(var t=this._click.bind(this),i=0,n=e.length;i<n;i++)e[i].addEventListener(WCF_CLICK_EVENT,t)},_click:function(t){var i=t.currentTarget;e.apiOnce({data:{actionName:"dismiss",className:"wcf\\data\\notice\\NoticeAction",objectIDs:[elData(i,"object-id")]},success:function(){elRemove(i.parentNode)}})}}}),define("WoltLabSuite/Core/Form/Builder/Field/Dependency/Manager",["Dictionary","Dom/ChangeListener","EventHandler","List","Dom/Traverse","Dom/Util","ObjectMap"],function(e,t,i,n,a,r,o){"use strict";var s=!1,l=!0,c=new n,d=new e,u=new n,h=new e,p=new o;return{_hide:function(t){elHide(t),c.add(t),t.classList.contains("tabMenuContent")&&elBySelAll("li",a.prevByClass(t,"tabMenu"),function(e){elData(e,"name")===elData(t,"name")&&elHide(e)}),elBySelAll("[max], [maxlength], [min], [required]",t,function(t){var i=new e,n=elAttr(t,"max");n&&(i.set("max",n),t.removeAttribute("max"));var a=elAttr(t,"maxlength");a&&(i.set("maxlength",a),t.removeAttribute("maxlength"));var r=elAttr(t,"min");r&&(i.set("min",r),t.removeAttribute("min")),t.required&&(i.set("required",!0),t.removeAttribute("required")),p.set(t,i)})},_show:function(e){elShow(e),c.delete(e),e.classList.contains("tabMenuContent")&&elBySelAll("li",a.prevByClass(e,"tabMenu"),function(t){elData(t,"name")===elData(e,"name")&&elShow(t)}),elBySelAll("input, select",e,function(t){for(var i=t.parentNode;i!==e&&"none"!==i.style.getPropertyValue("display");)i=i.parentNode;if(i===e&&p.has(t)){var n=p.get(t);n.has("max")&&elAttr(t,"max",n.get("max")),n.has("maxlength")&&elAttr(t,"maxlength",n.get("maxlength")),n.has("min")&&elAttr(t,"min",n.get("min")),n.has("required")&&elAttr(t,"required",""),p.delete(t)}})},addDependency:function(e){var t=e.getDependentNode();h.has(t.id)?h.get(t.id).push(e):h.set(t.id,[e]);for(var i=e.getFields(),n=0,a=i.length;n<a;n++){var o=i[n],s=r.identify(o);d.has(s)||(d.set(s,o),"INPUT"!==o.tagName||"checkbox"!==o.type&&"radio"!==o.type?o.addEventListener("input",this.checkDependencies.bind(this)):o.addEventListener("change",this.checkDependencies.bind(this)))}},checkDependencies:function(){var e=[];h.forEach(function(t,i){var n=elById(i);if(null===n)return void e.push(n);for(var a=0,r=t.length;a<r;a++)if(!t[a].checkDependency())return void this._hide(n);this._show(n)}.bind(this));for(var t=0,i=e.length;t<i;t++)h.delete(e.id);this.checkContainers()},addContainerCheckCallback:function(e){if("function"!=typeof e)throw new TypeError("Expected a valid callback for parameter 'callback'.");i.add("com.woltlab.wcf.form.builder.dependency","checkContainers",e)},checkContainers:function(){if(!0===s)return void(l=!0);s=!0,l=!1,i.fire("com.woltlab.wcf.form.builder.dependency","checkContainers"),s=!1,l&&this.checkContainers()},isHiddenByDependencies:function(e){if(c.has(e))return!0;var t=!1;return c.forEach(function(i){r.contains(i,e)&&(t=!0)}),t},register:function(e){var t=elById(e);if(null===t)throw new Error("Unknown element with id '"+e+"'");if(u.has(t))throw new Error("Form with id '"+e+"' has already been registered.");u.add(t)},unregister:function(e){var t=elById(e);if(null===t)throw new Error("Unknown element with id '"+e+"'");if(!u.has(t))throw new Error("Form with id '"+e+"' has not been registered.");u.delete(t),c.forEach(function(e){t.contains(e)&&c.delete(e)}),h.forEach(function(e,i){t.contains(elById(i))&&h.delete(i);for(var n=0,a=e.length;n<a;n++)for(var r=e[n].getFields(),o=0,a=r.length;o<a;o++){var s=r[o];d.delete(s.id),p.delete(s)}})}}}),define("WoltLabSuite/Core/Form/Builder/Field/Field",[],function(){"use strict";function e(e){this.init(e)}return e.prototype={init:function(e){this._fieldId=e,this._readField()},_getData:function(){throw new Error("Missing implementation of WoltLabSuite/Core/Form/Builder/Field/Field._getData!")},_readField:function(){if(this._field=elById(this._fieldId),null===this._field)throw new Error("Unknown field with id '"+this._fieldId+"'.")},destroy:function(){},getData:function(){return Promise.resolve(this._getData())},getId:function(){return this._fieldId}},e}),define("WoltLabSuite/Core/Form/Builder/Manager",["Core","Dictionary","./Field/Dependency/Manager","./Field/Field"],function(e,t,i,n){"use strict";var a=new t,r=new t;return{getData:function(t){if(!this.hasForm(t))throw new Error("Unknown form with id '"+t+"'.");var i=[];return a.get(t).forEach(function(e){var t=e.getData();if(!(t instanceof Promise))throw new TypeError("Data for field with id '"+e.getId()+"' is no promise.");i.push(t)}),Promise.all(i).then(function(t){for(var i={},n=0,a=t.length;n<a;n++)i=e.extend(i,t[n]);return i})},getForm:function(e){if(!this.hasForm(e))throw new Error("Unknown form with id '"+e+"'.");return r.get(e)},hasField:function(e,t){if(!this.hasForm(e))throw new Error("Unknown form with id '"+e+"'.");return a.get(e).has(t)},hasForm:function(e){return r.has(e)},registerField:function(e,t){if(!this.hasForm(e))throw new Error("Unknown form with id '"+e+"'.");if(!(t instanceof n))throw new Error("Add field is no instance of 'WoltLabSuite/Core/Form/Builder/Field/Field'.");var i=t.getId();if(this.hasField(e,i))throw new Error("Form field with id '"+i+"' has already been registered for form with id '"+i+"'.");a.get(e).set(i,t)},registerForm:function(e){if(this.hasForm(e))throw new Error("Form with id '"+e+"' has already been registered.");var i=elById(e);if(null===i)throw new Error("Unknown form with id '"+e+"'.");r.set(e,i),a.set(e,new t)},unregisterForm:function(e){if(!this.hasForm(e))throw new Error("Unknown form with id '"+e+"'.");r.delete(e),a.get(e).forEach(function(e){e.destroy()}),a.delete(e),i.unregister(e)}}}),define("WoltLabSuite/Core/Form/Builder/Dialog",["Ajax","Core","./Manager","Ui/Dialog"],function(e,t,i,n){"use strict";function a(e,t,i,n){this.init(e,t,i,n)}return a.prototype={init:function(e,i,n,a){this._dialogId=e,this._className=i,this._actionName=n,this._options=t.extend({actionParameters:{},destroyOnClose:!1,usesDboAction:this._className.match(/\w+\\data\\/)},a),this._options.dialog=t.extend(this._options.dialog||{},{onClose:this._dialogOnClose.bind(this)}),this._formId="",this._dialogContent=""},_ajaxSetup:function(){var e={data:{actionName:this._actionName,className:this._className,parameters:this._options.actionParameters}};return this._options.usesDboAction||(e.url="index.php?ajax-invoke/&t="+SECURITY_TOKEN,e.withCredentials=!0),e},_ajaxSuccess:function(e){switch(e.actionName){case this._actionName:if(void 0===e.returnValues)throw new Error("Missing return data.");if(void 0===e.returnValues.dialog)throw new Error("Missing dialog template in return data.");if(void 0===e.returnValues.formId)throw new Error("Missing form id in return data.");this._openDialogContent(e.returnValues.formId,e.returnValues.dialog);break;case this._options.submitActionName:if(e.returnValues&&e.returnValues.formId&&e.returnValues.dialog){if(e.returnValues.formId!==this._formId)throw new Error("Mismatch between form ids: expected '"+this._formId+"' but got '"+e.returnValues.formId+"'.");this._openDialogContent(e.returnValues.formId,e.returnValues.dialog)}else this.destroy(),"function"==typeof this._options.successCallback&&this._options.successCallback(e.returnValues||{});break;default:throw new Error("Cannot handle action '"+e.actionName+"'.")}},_closeDialog:function(){n.close(this)},_dialogOnClose:function(){this._options.destroyOnClose&&this.destroy()},_dialogSetup:function(){return{id:this._dialogId,options:this._options.dialog,source:this._dialogContent}},_dialogSubmit:function(){this.getData().then(this._submitForm.bind(this))},_openDialogContent:function(e,t){this.destroy(!0),this._formId=e,this._dialogContent=t;var i=n.open(this,this._dialogContent),a=elBySel("button[data-type=cancel]",i.content);null===a||elDataBool(a,"has-event-listener")||(a.addEventListener("click",this._closeDialog.bind(this)),elData(a,"has-event-listener",1))},_submitForm:function(t){var i=elBySel("button[data-type=submit]",n.getDialog(this).content);"function"==typeof this._options.onSubmit?this._options.onSubmit(t,i):"string"==typeof this._options.submitActionName&&(i.disabled=!0,e.api(this,{actionName:this._options.submitActionName,parameters:{data:t,formId:this._formId}}))},destroy:function(e){""!==this._formId&&(i.hasForm(this._formId)&&i.unregisterForm(this._formId),!0!==e&&n.destroy(this))},getData:function(){if(""===this._formId)throw new Error("Form has not been requested yet.");return i.getData(this._formId)},open:function(){n.getDialog(this._dialogId)?n.openStatic(this._dialogId):e.api(this)}},a}),define("WoltLabSuite/Core/Media/Manager/Search",["Ajax","Core","Dom/Traverse","Dom/Util","EventKey","Language","Ui/SimpleDropdown"],function(e,t,i,n,a,r,o){"use strict";function s(e){this._mediaManager=e,this._searchMode=!1,this._searchContainer=elByClass("mediaManagerSearch",e.getDialog())[0],this._input=elByClass("mediaManagerSearchField",e.getDialog())[0],this._input.addEventListener("keypress",this._keyPress.bind(this)),this._cancelButton=elByClass("mediaManagerSearchCancelButton",e.getDialog())[0],this._cancelButton.addEventListener(WCF_CLICK_EVENT,this._cancelSearch.bind(this))}return s.prototype={_ajaxSetup:function(){return{data:{actionName:"getSearchResultList",className:"wcf\\data\\media\\MediaAction",interfaceName:"wcf\\data\\ISearchAction"}}},_ajaxSuccess:function(e){this._mediaManager.setMedia(e.returnValues.media||{},e.returnValues.template||"",{pageCount:e.returnValues.pageCount||0,pageNo:e.returnValues.pageNo||0}),elByClass("dialogContent",this._mediaManager.getDialog())[0].scrollTop=0},_cancelSearch:function(){this._searchMode&&(this._searchMode=!1,this.resetSearch(),this._mediaManager.resetMedia())},_hideStringThresholdError:function(){var e=i.childByClass(this._input.parentNode.parentNode,"innerInfo");e&&elHide(e)},_keyPress:function(e){a.Enter(e)&&(e.preventDefault(),this._input.value.length>=this._mediaManager.getOption("minSearchLength")?(this._hideStringThresholdError(),this.search()):this._showStringThresholdError())},_showStringThresholdError:function(){var e=i.childByClass(this._input.parentNode.parentNode,"innerInfo");e?elShow(e):(e=elCreate("p"),e.className="innerInfo",e.textContent=r.get("wcf.media.search.info.searchStringThreshold",{minSearchLength:this._mediaManager.getOption("minSearchLength")}),n.insertAfter(e,this._input.parentNode))},hideSearch:function(){elHide(this._searchContainer)},resetSearch:function(){this._input.value=""},showSearch:function(){elShow(this._searchContainer)},search:function(t){"number"!=typeof t&&(t=1);var i=this._input.value;i&&this._input.value.length<this._mediaManager.getOption("minSearchLength")?(this._showStringThresholdError(),i=""):this._hideStringThresholdError(),this._searchMode=!0,e.api(this,{parameters:{categoryID:this._mediaManager.getCategoryId(),imagesOnly:this._mediaManager.getOption("imagesOnly"),mode:this._mediaManager.getMode(),pageNo:t,searchString:i}})}},s}),define("WoltLabSuite/Core/Media/Manager/Base",["Core","Dictionary","Dom/ChangeListener","Dom/Traverse","Dom/Util","EventHandler","Language","List","Permission","Ui/Dialog","Ui/Notification","WoltLabSuite/Core/Controller/Clipboard","WoltLabSuite/Core/Media/Editor","WoltLabSuite/Core/Media/Upload","WoltLabSuite/Core/Media/Manager/Search","StringUtil","WoltLabSuite/Core/Ui/Pagination","WoltLabSuite/Core/Media/Clipboard"],function(e,t,i,n,a,r,o,s,l,c,d,u,h,p,f,m,g,v){"use strict";function _(n){this._options=e.extend({dialogTitle:o.get("wcf.media.manager"),imagesOnly:!1,minSearchLength:3},n),this._id="mediaManager"+b++,this._listItems=new t,this._media=new t,this._mediaManagerMediaList=null,this._search=null,this._upload=null,this._forceClipboard=!1,this._hadInitiallyMarkedItems=!1,this._pagination=null,l.get("admin.content.cms.canManageMedia")&&(this._mediaEditor=new h(this)),i.add("WoltLabSuite/Core/Media/Manager",this._addButtonEventListeners.bind(this)),r.add("com.woltlab.wcf.media.upload","success",this._openEditorAfterUpload.bind(this))}var b=0;return _.prototype={_addButtonEventListeners:function(){if(this._mediaManagerMediaList)for(var e=n.childrenByTag(this._mediaManagerMediaList,"LI"),t=0,i=e.length;t<i;t++){var a=e[t];if(l.get("admin.content.cms.canManageMedia")){var r=elByClass("jsMediaEditButton",a)[0];r&&(r.classList.remove("jsMediaEditButton"),r.addEventListener(WCF_CLICK_EVENT,this._editMedia.bind(this)))}}},_categoryChange:function(){this._search.search()},_click:function(e){e.preventDefault(),c.open(this)},_dialogClose:function(){(l.get("admin.content.cms.canManageMedia")||this._forceClipboard)&&u.hideEditor("com.woltlab.wcf.media")},_dialogInit:function(e,t){var i=t.returnValues.media||{};for(var n in i)objOwns(i,n)&&this._media.set(~~n,i[n]);this._initPagination(~~t.returnValues.pageCount),this._hadInitiallyMarkedItems=t.returnValues.hasMarkedItems},_dialogSetup:function(){return{id:this._id,options:{onClose:this._dialogClose.bind(this),onShow:this._dialogShow.bind(this),title:this._options.dialogTitle},source:{after:this._dialogInit.bind(this),data:{actionName:"getManagementDialog",className:"wcf\\data\\media\\MediaAction",parameters:{mode:this.getMode(),imagesOnly:this._options.imagesOnly}}}}},_dialogShow:function(){if(!this._mediaManagerMediaList){var e=this.getDialog();this._mediaManagerMediaList=elByClass("mediaManagerMediaList",e)[0],this._mediaCategorySelect=elBySel(".mediaManagerCategoryList > select",e),this._mediaCategorySelect&&this._mediaCategorySelect.addEventListener("change",this._categoryChange.bind(this));for(var t=n.childrenByTag(this._mediaManagerMediaList,"LI"),i=0,r=t.length;i<r;i++){var o=t[i];this._listItems.set(~~elData(o,"object-id"),o)}if(l.get("admin.content.cms.canManageMedia")){var s=elByClass("mediaManagerMediaUploadButton",c.getDialog(this).dialog)[0];this._upload=new p(a.identify(s),a.identify(this._mediaManagerMediaList),{mediaManager:this});new WCF.Action.Delete("wcf\\data\\media\\MediaAction",".mediaFile")._didTriggerEffect=function(e){this.removeMedia(elData(e[0],"object-id"))}.bind(this)}l.get("admin.content.cms.canManageMedia")||this._forceClipboard?v.init("menuManagerDialog-"+this.getMode(),!!this._hadInitiallyMarkedItems,this):this._removeClipboardCheckboxes(),this._search=new f(this),t.length||this._search.hideSearch()}(l.get("admin.content.cms.canManageMedia")||this._forceClipboard)&&u.showEditor("com.woltlab.wcf.media")},_editMedia:function(e){if(!l.get("admin.content.cms.canManageMedia"))throw new Error("You are not allowed to edit media files.");c.close(this),this._mediaEditor.edit(this._media.get(~~elData(e.currentTarget,"object-id")))},_editorClose:function(){c.open(this)},_editorSuccess:function(e,t){if(this._mediaCategorySelect){var i=~~this._mediaCategorySelect.value;if(i){var n=~~e.categoryID;t==n||t!=i&&n!=i||this._search.search()}}c.open(this),this._media.set(~~e.mediaID,e);var a=this._listItems.get(~~e.mediaID),r=elByClass("mediaTitle",a)[0];e.isMultilingual?r.textContent=e.title[LANGUAGE_ID]||e.filename:r.textContent=e.title[e.languageID]||e.filename},_initPagination:function(e,t){if(void 0===t&&(t=1),e>1){var i=elCreate("div");i.className="paginationBottom jsPagination",a.replaceElement(elBySel(".jsPagination",c.getDialog(this).content),i),this._pagination=new g(i,{activePage:t,callbackSwitch:this._search.search.bind(this._search),maxPage:e})}else this._pagination&&elHide(this._pagination.getElement())},_removeClipboardCheckboxes:function(){for(var e=elByClass("mediaCheckbox",this._mediaManagerMediaList);e.length;)elRemove(e[0])},_openEditorAfterUpload:function(e){if(e.upload===this._upload&&!e.isMultiFileUpload&&!this._upload.hasPendingUploads()){var t=Object.keys(e.media);t.length&&(c.close(this),this._mediaEditor.edit(this._media.get(~~e.media[t[0]].mediaID)))}},_setMedia:function(r){e.isPlainObject(r)?this._media=t.fromObject(r):this._media=r;var s=n.nextByClass(this._mediaManagerMediaList,"info");this._media.size?s&&elHide(s):(null===s&&(s=elCreate("p"),s.className="info",s.textContent=o.get("wcf.media.search.noResults")),elShow(s),a.insertAfter(s,this._mediaManagerMediaList));for(var c=n.childrenByTag(this._mediaManagerMediaList,"LI"),d=0,h=c.length;d<h;d++){var p=c[d];this._media.has(elData(p,"object-id"))?elShow(p):elHide(p)}i.trigger(),l.get("admin.content.cms.canManageMedia")||this._forceClipboard?u.reload():this._removeClipboardCheckboxes()},addMedia:function(e,t){e.languageID||(e.isMultilingual=1),this._media.set(~~e.mediaID,e),this._listItems.set(~~e.mediaID,t),1===this._listItems.size&&this._search.showSearch()},clipboardDeleteMedia:function(e){for(var t=0,i=e.length;t<i;t++)this.removeMedia(~~e[t],!0);d.show()},getCategoryId:function(){return this._mediaCategorySelect?this._mediaCategorySelect.value:0},getDialog:function(){return c.getDialog(this).dialog},getMode:function(){return""},getOption:function(e){return this._options[e]?this._options[e]:null},removeMedia:function(e){if(this._listItems.has(e)){try{elRemove(this._listItems.get(e))}catch(e){}this._listItems.delete(e),this._media.delete(e)}},resetMedia:function(){this._search.search()},setMedia:function(e,t,i){var a=!1;for(var r in e)objOwns(e,r)&&(a=!0);if(a){var o=elCreate("ul");o.innerHTML=t;for(var s=n.childrenByTag(o,"LI"),l=0,c=s.length;l<c;l++){var d=s[l];this._listItems.has(~~elData(d,"object-id"))||(this._listItems.set(elData(d,"object-id"),d),this._mediaManagerMediaList.appendChild(d))}}this._initPagination(i.pageCount,i.pageNo),this._setMedia(e)},setupMediaElement:function(t,i){var a=n.childByClass(i,"mediaInformation"),r=elCreate("nav");r.className="jsMobileNavigation buttonGroupNavigation",a.parentNode.appendChild(r);var s=elCreate("ul");s.className="buttonList iconList",r.appendChild(s);var c=elCreate("li");c.className="mediaCheckbox",s.appendChild(c);var d=elCreate("a");c.appendChild(d);var u=elCreate("label");d.appendChild(u);var h=elCreate("input");if(h.className="jsClipboardItem",elAttr(h,"type","checkbox"),elData(h,"object-id",t.mediaID),u.appendChild(h),l.get("admin.content.cms.canManageMedia")){c=elCreate("li"),c.className="jsMediaEditButton",elData(c,"object-id",t.mediaID),s.appendChild(c),c.innerHTML='<a><span class="icon icon16 fa-pencil jsTooltip" title="'+o.get("wcf.global.button.edit")+'"></span> <span class="invisible">'+o.get("wcf.global.button.edit")+"</span></a>",c=elCreate("li"),c.className="jsDeleteButton",elData(c,"object-id",t.mediaID);var p=e.getUuid();elData(c,"confirm-message-html",m.unescapeHTML(o.get("wcf.media.delete.confirmMessage",{title:p})).replace(p,m.escapeHTML(t.filename))),s.appendChild(c),c.innerHTML='<a><span class="icon icon16 fa-times jsTooltip" title="'+o.get("wcf.global.button.delete")+'"></span> <span class="invisible">'+o.get("wcf.global.button.delete")+"</span></a>"}}},_}),define("WoltLabSuite/Core/Media/Manager/Editor",["Core","Dictionary","Dom/Traverse","EventHandler","Language","Permission","Ui/Dialog","WoltLabSuite/Core/Controller/Clipboard","WoltLabSuite/Core/Media/Manager/Base"],function(e,t,i,n,a,r,o,s,l){"use strict";function c(i){i=e.extend({callbackInsert:null},i),l.call(this,i),this._forceClipboard=!0,this._activeButton=null;var a=this._options.editor?this._options.editor.core.toolbar()[0]:void 0;this._buttons=elByClass(this._options.buttonClass||"jsMediaEditorButton",a);for(var r=0,o=this._buttons.length;r<o;r++)this._buttons[r].addEventListener(WCF_CLICK_EVENT,this._click.bind(this));if(this._mediaToInsert=new t,this._mediaToInsertByClipboard=!1,this._uploadData=null,this._uploadId=null,this._options.editor&&!this._options.editor.opts.woltlab.attachments){
+var s=elData(this._options.editor.$editor[0],"element-id"),c=n.add("com.woltlab.wcf.redactor2","dragAndDrop_"+s,this._editorUpload.bind(this)),d=n.add("com.woltlab.wcf.redactor2","pasteFromClipboard_"+s,this._editorUpload.bind(this));n.add("com.woltlab.wcf.redactor2","destory_"+s,function(){n.remove("com.woltlab.wcf.redactor2","dragAndDrop_"+s,c),n.remove("com.woltlab.wcf.redactor2","dragAndDrop_"+s,d)}),n.add("com.woltlab.wcf.media.upload","success",this._mediaUploaded.bind(this))}}return e.inherit(c,l,{_addButtonEventListeners:function(){if(c._super.prototype._addButtonEventListeners.call(this),this._mediaManagerMediaList)for(var e=i.childrenByTag(this._mediaManagerMediaList,"LI"),t=0,n=e.length;t<n;t++){var a=e[t],r=elByClass("jsMediaInsertButton",a)[0];r&&(r.classList.remove("jsMediaInsertButton"),r.addEventListener(WCF_CLICK_EVENT,this._openInsertDialog.bind(this)))}},_buildInsertDialog:function(){for(var e="",t=this._getThumbnailSizes(),i=0,n=t.length;i<n;i++)e+='<option value="'+t[i]+'">'+a.get("wcf.media.insert.imageSize."+t[i])+"</option>";e+='<option value="original">'+a.get("wcf.media.insert.imageSize.original")+"</option>";var r='<div class="section"><dl class="thumbnailSizeSelection"><dt>'+a.get("wcf.media.insert.imageSize")+'</dt><dd><select name="thumbnailSize">'+e+'</select></dd></dl></div><div class="formSubmit"><button class="buttonPrimary">'+a.get("wcf.global.button.insert")+"</button></div>";o.open({_dialogSetup:function(){return{id:this._getInsertDialogId(),options:{onClose:this._editorClose.bind(this),onSetup:function(e){elByClass("buttonPrimary",e)[0].addEventListener(WCF_CLICK_EVENT,this._insertMedia.bind(this));var t=elBySel(".thumbnailSizeSelection",e);elShow(t)}.bind(this),title:a.get("wcf.media.insert")},source:r}}.bind(this)})},_click:function(e){this._activeButton=e.currentTarget,c._super.prototype._click.call(this,e)},_dialogShow:function(){c._super.prototype._dialogShow.call(this),this._uploadData&&(this._uploadData.file?this._upload.uploadFile(this._uploadData.file):this._uploadId=this._upload.uploadBlob(this._uploadData.blob),this._uploadData=null)},_editorUpload:function(e){this._uploadData=e,o.open(this)},_getInsertDialogId:function(){var e="mediaInsert";return this._mediaToInsert.forEach(function(t,i){e+="-"+i}),e},_getThumbnailSizes:function(){for(var e,t,i=[],n=["small","medium","large"],a=0,r=n.length;a<r;a++)e=n[a],t=!0,this._mediaToInsert.forEach(function(i){i[e+"ThumbnailType"]||(t=!1)}),t&&i.push(e);return i},_insertMedia:function(e,i,n){void 0===n&&(n=!0);if(e){o.close(this._getInsertDialogId());var a=e.currentTarget.closest(".dialogContent");i=elBySel("select[name=thumbnailSize]",a).value}if(null!==this._options.callbackInsert?this._options.callbackInsert(this._mediaToInsert,"separate",i):(this._options.editor.buffer.set(),this._mediaToInsert.forEach(this._insertMediaItem.bind(this,i))),this._mediaToInsertByClipboard){var r=[];this._mediaToInsert.forEach(function(e){r.push(e.mediaID)}),s.unmark("com.woltlab.wcf.media",r)}this._mediaToInsert=new t,this._mediaToInsertByClipboard=!1,n&&o.close(this)},_insertMediaGallery:function(){var e=[];this._mediaToInsert.forEach(function(t){e.push(t.mediaID)}),this._options.editor.buffer.set(),this._options.editor.insert.text("[wsmg='"+e.join(",")+"'][/wsmg]")},_insertMediaItem:function(e,t){if(t.isImage){for(var i,n=["small","medium","large","original"],a="",r=0;r<4&&(i=n[r],0==t[i+"ThumbnailHeight"]||(a=i,e!=i));r++);e=a,e||(e="original");var o=t.link;"original"!==e&&(o=t[e+"ThumbnailLink"]),this._options.editor.insert.html('<img src="'+o+'" class="woltlabSuiteMedia" data-media-id="'+t.mediaID+'" data-media-size="'+e+'">')}else this._options.editor.insert.text("[wsm='"+t.mediaID+"'][/wsm]")},_mediaUploaded:function(e){null!==this._uploadId&&this._upload===e.upload&&(this._uploadId===e.uploadId||Array.isArray(this._uploadId)&&-1!==this._uploadId.indexOf(e.uploadId))&&(this._mediaToInsert=t.fromObject(e.media),this._insertMedia(null,"medium",!1),this._uploadId=null)},_openInsertDialog:function(e){this.insertMedia([~~elData(e.currentTarget,"object-id")])},clipboardInsertMedia:function(e){this.insertMedia(e,!0)},insertMedia:function(e,i){this._mediaToInsert=new t,this._mediaToInsertByClipboard=i||!1;for(var n,a=!0,r=0,s=e.length;r<s;r++)n=this._media.get(e[r]),this._mediaToInsert.set(n.mediaID,n),n.isImage||(a=!1);if(a){if(this._getThumbnailSizes().length){o.close(this);var l=this._getInsertDialogId();o.getDialog(l)?o.openStatic(l):this._buildInsertDialog()}else this._insertMedia(void 0,"original")}else this._insertMedia()},getMode:function(){return"editor"},setupMediaElement:function(e,t){c._super.prototype.setupMediaElement.call(this,e,t);var i=elBySel("nav.buttonGroupNavigation > ul",t),n=elCreate("li");n.className="jsMediaInsertButton",elData(n,"object-id",e.mediaID),i.appendChild(n),n.innerHTML='<a><span class="icon icon16 fa-plus jsTooltip" title="'+a.get("wcf.media.button.insert")+'"></span> <span class="invisible">'+a.get("wcf.media.button.insert")+"</span></a>"}}),c}),define("WoltLabSuite/Core/Media/Manager/Select",["Core","Dom/Traverse","Dom/Util","Language","ObjectMap","Ui/Dialog","WoltLabSuite/Core/FileUtil","WoltLabSuite/Core/Media/Manager/Base"],function(e,t,i,n,a,r,o,s){"use strict";function l(e){s.call(this,e),this._activeButton=null,this._buttons=elByClass(this._options.buttonClass||"jsMediaSelectButton"),this._storeElements=new a;for(var t=0,n=this._buttons.length;t<n;t++){var r=this._buttons[t],o=elData(r,"store");if(o){var l=elById(o);if(l&&"INPUT"===l.tagName){this._buttons[t].addEventListener(WCF_CLICK_EVENT,this._click.bind(this)),this._storeElements.set(r,l);var c=elCreate("p");c.className="button",i.insertAfter(c,r);var d=elCreate("span");d.className="icon icon16 fa-times",c.appendChild(d),l.value||elHide(c),c.addEventListener(WCF_CLICK_EVENT,this._removeMedia.bind(this))}}}}return e.inherit(l,s,{_addButtonEventListeners:function(){if(l._super.prototype._addButtonEventListeners.call(this),this._mediaManagerMediaList)for(var e=t.childrenByTag(this._mediaManagerMediaList,"LI"),i=0,n=e.length;i<n;i++){var a=e[i],r=elByClass("jsMediaSelectButton",a)[0];r&&(r.classList.remove("jsMediaSelectButton"),r.addEventListener(WCF_CLICK_EVENT,this._chooseMedia.bind(this)))}},_chooseMedia:function(e){if(null===this._activeButton)throw new Error("Media cannot be chosen if no button is active.");var t=this._media.get(~~elData(e.currentTarget,"object-id"));elById(elData(this._activeButton,"store")).value=t.mediaID;var i=elData(this._activeButton,"display");if(i){var n=elById(i);if(n)if(t.isImage)n.innerHTML='<img src="'+(t.smallThumbnailLink?t.smallThumbnailLink:t.link)+'" alt="'+(t.altText&&t.altText[LANGUAGE_ID]?t.altText[LANGUAGE_ID]:"")+'" />';else{var a=o.getIconNameByFilename(t.filename);a&&(a="-"+a),n.innerHTML='<div class="box48" style="margin-bottom: 10px;"><span class="icon icon48 fa-file'+a+'-o"></span><div class="containerHeadline"><h3>'+t.filename+"</h3><p>"+t.formattedFilesize+"</p></div></div>"}}elShow(this._activeButton.nextElementSibling),r.close(this)},_click:function(e){if(e.preventDefault(),this._activeButton=e.currentTarget,l._super.prototype._click.call(this,e),this._mediaManagerMediaList)for(var i,n=this._storeElements.get(this._activeButton),a=t.childrenByTag(this._mediaManagerMediaList,"LI"),r=0,o=a.length;r<o;r++)i=a[r],n.value&&n.value==elData(i,"object-id")?i.classList.add("jsSelected"):i.classList.remove("jsSelected")},getMode:function(){return"select"},setupMediaElement:function(e,t){l._super.prototype.setupMediaElement.call(this,e,t);var i=elBySel("nav.buttonGroupNavigation > ul",t),a=elCreate("li");a.className="jsMediaSelectButton",elData(a,"object-id",e.mediaID),i.appendChild(a),a.innerHTML='<a><span class="icon icon16 fa-check jsTooltip" title="'+n.get("wcf.media.button.select")+'"></span> <span class="invisible">'+n.get("wcf.media.button.select")+"</span></a>"},_removeMedia:function(e){e.preventDefault();var t=e.currentTarget;elHide(t);var i=t.previousElementSibling;elById(elData(i,"store")).value=0;var n=elData(i,"display");if(n){var a=elById(n);a&&(a.innerHTML="")}}}),l}),define("WoltLabSuite/Core/Ui/Search/Input",["Ajax","Core","EventKey","Dom/Util","Ui/SimpleDropdown"],function(e,t,i,n,a){"use strict";function r(e,t){this.init(e,t)}return r.prototype={init:function(e,i){if(this._element=e,!(this._element instanceof Element))throw new TypeError("Expected a valid DOM element.");if("INPUT"!==this._element.nodeName||"search"!==this._element.type&&"text"!==this._element.type)throw new Error('Expected an input[type="text"].');this._activeItem=null,this._dropdownContainerId="",this._lastValue="",this._list=null,this._request=null,this._timerDelay=null,this._options=t.extend({ajax:{actionName:"getSearchResultList",className:"",interfaceName:"wcf\\data\\ISearchAction"},callbackDropdownInit:null,callbackSelect:null,delay:500,excludedSearchValues:[],minLength:3,noResultPlaceholder:"",preventSubmit:!1},i),elAttr(this._element,"autocomplete","off"),this._element.addEventListener("keydown",this._keydown.bind(this)),this._element.addEventListener("keyup",this._keyup.bind(this))},addExcludedSearchValues:function(e){-1===this._options.excludedSearchValues.indexOf(e)&&this._options.excludedSearchValues.push(e)},removeExcludedSearchValues:function(e){var t=this._options.excludedSearchValues.indexOf(e);-1!==t&&this._options.excludedSearchValues.splice(t,1)},_keydown:function(e){(null!==this._activeItem&&a.isOpen(this._dropdownContainerId)||this._options.preventSubmit)&&i.Enter(e)&&e.preventDefault(),(i.ArrowUp(e)||i.ArrowDown(e)||i.Escape(e))&&e.preventDefault()},_keyup:function(e){if(null!==this._activeItem)if(a.isOpen(this._dropdownContainerId)){if(i.ArrowUp(e))return e.preventDefault(),this._keyboardPreviousItem();if(i.ArrowDown(e))return e.preventDefault(),this._keyboardNextItem();if(i.Enter(e))return e.preventDefault(),this._keyboardSelectItem()}else this._activeItem=null;if(i.Escape(e))return void a.close(this._dropdownContainerId);var t=this._element.value.trim();if(this._lastValue!==t){if(this._lastValue=t,t.length<this._options.minLength)return void(this._dropdownContainerId&&(a.close(this._dropdownContainerId),this._activeItem=null));this._options.delay?(null!==this._timerDelay&&window.clearTimeout(this._timerDelay),this._timerDelay=window.setTimeout(function(){this._search(t)}.bind(this),this._options.delay)):this._search(t)}},_search:function(t){this._request&&this._request.abortPrevious(),this._request=e.api(this,this._getParameters(t))},_getParameters:function(e){return{parameters:{data:{excludedSearchValues:this._options.excludedSearchValues,searchString:e}}}},_keyboardNextItem:function(){this._activeItem.classList.remove("active"),this._activeItem.nextElementSibling?this._activeItem=this._activeItem.nextElementSibling:this._activeItem=this._list.children[0],this._activeItem.classList.add("active")},_keyboardPreviousItem:function(){this._activeItem.classList.remove("active"),this._activeItem.previousElementSibling?this._activeItem=this._activeItem.previousElementSibling:this._activeItem=this._list.children[this._list.childElementCount-1],this._activeItem.classList.add("active")},_keyboardSelectItem:function(){this._selectItem(this._activeItem)},_clickSelectItem:function(e){this._selectItem(e.currentTarget)},_selectItem:function(e){this._options.callbackSelect&&!1===this._options.callbackSelect(e)?this._element.value="":this._element.value=elData(e,"label"),this._activeItem=null,a.close(this._dropdownContainerId)},_ajaxSuccess:function(e){var t=!1;if(null===this._list?(this._list=elCreate("ul"),this._list.className="dropdownMenu",t=!0,"function"==typeof this._options.callbackDropdownInit&&this._options.callbackDropdownInit(this._list)):this._list.innerHTML="","object"==typeof e.returnValues){var i,r=this._clickSelectItem.bind(this);for(var o in e.returnValues)e.returnValues.hasOwnProperty(o)&&(i=this._createListItem(e.returnValues[o]),i.addEventListener(WCF_CLICK_EVENT,r),this._list.appendChild(i))}t&&(n.insertAfter(this._list,this._element),a.initFragment(this._element.parentNode,this._list),this._dropdownContainerId=n.identify(this._element.parentNode)),this._dropdownContainerId&&(this._activeItem=null,this._list.childElementCount||!1!==this._handleEmptyResult()?(a.open(this._dropdownContainerId,!0),this._list.childElementCount&&~~elData(this._list.children[0],"object-id")&&(this._activeItem=this._list.children[0],this._activeItem.classList.add("active"))):a.close(this._dropdownContainerId))},_handleEmptyResult:function(){if(!this._options.noResultPlaceholder)return!1;var e=elCreate("li");e.className="dropdownText";var t=elCreate("span");return t.textContent=this._options.noResultPlaceholder,e.appendChild(t),this._list.appendChild(e),!0},_createListItem:function(e){var t=elCreate("li");elData(t,"object-id",e.objectID),elData(t,"label",e.label);var i=elCreate("span");return i.textContent=e.label,t.appendChild(i),t},_ajaxSetup:function(){return{data:this._options.ajax}}},r}),define("WoltLabSuite/Core/Ui/User/Search/Input",["Core","WoltLabSuite/Core/Ui/Search/Input"],function(e,t){"use strict";function i(e,t){this.init(e,t)}return e.inherit(i,t,{init:function(t,n){var a=e.isPlainObject(n)&&!0===n.includeUserGroups;n=e.extend({ajax:{className:"wcf\\data\\user\\UserAction",parameters:{data:{includeUserGroups:a?1:0}}}},n),i._super.prototype.init.call(this,t,n)},_createListItem:function(e){var t=i._super.prototype._createListItem.call(this,e);elData(t,"type",e.type);var n=elCreate("div");return n.className="box16",n.innerHTML="group"===e.type?'<span class="icon icon16 fa-users"></span>':e.icon,n.appendChild(t.children[0]),t.appendChild(n),t}}),i}),define("WoltLabSuite/Core/Ui/Acl/Simple",["Language","StringUtil","Dom/ChangeListener","WoltLabSuite/Core/Ui/User/Search/Input"],function(e,t,i,n){"use strict";function a(e,t){this.init(e,t)}return a.prototype={init:function(e,t){this._prefix=e||"",this._inputName=t||"aclValues",this._build()},_build:function(){var e=elById(this._prefix+"aclInputContainer");elById(this._prefix+"aclAllowAll").addEventListener("change",function(){elHide(e)}),elById(this._prefix+"aclAllowAll_no").addEventListener("change",function(){elShow(e)}),this._list=elById(this._prefix+"aclAccessList"),this._list.addEventListener(WCF_CLICK_EVENT,this._removeItem.bind(this));var t=[];elBySelAll(".aclLabel",this._list,function(e){t.push(e.textContent)}),this._searchInput=new n(elById(this._prefix+"aclSearchInput"),{callbackSelect:this._select.bind(this),includeUserGroups:!0,excludedSearchValues:t,preventSubmit:!0}),this._aclListContainer=elById(this._prefix+"aclListContainer"),i.trigger()},_select:function(n){var a=elData(n,"type"),r=elData(n,"label"),o='<span class="icon icon16 fa-'+("group"===a?"users":"user")+'"></span>';o+='<span class="aclLabel">'+t.escapeHTML(r)+"</span>",o+='<span class="icon icon16 fa-times pointer jsTooltip" title="'+e.get("wcf.global.button.delete")+'"></span>',o+='<input type="hidden" name="'+this._inputName+"["+a+'][]" value="'+elData(n,"object-id")+'">';var s=elCreate("li");s.innerHTML=o;var l=elBySel(".fa-user",this._list);return null===l?this._list.appendChild(s):this._list.insertBefore(s,l.parentNode),elShow(this._aclListContainer),this._searchInput.addExcludedSearchValues(r),i.trigger(),!1},_removeItem:function(e){if(e.target.classList.contains("fa-times")){var t=elBySel(".aclLabel",e.target.parentNode);this._searchInput.removeExcludedSearchValues(t.textContent),elRemove(e.target.parentNode),0===this._list.childElementCount&&elHide(this._aclListContainer)}}},a}),define("WoltLabSuite/Core/Ui/Article/MarkAllAsRead",["Ajax"],function(e){"use strict";return{init:function(){elBySelAll(".markAllAsReadButton",void 0,function(e){e.addEventListener(WCF_CLICK_EVENT,this._click.bind(this))}.bind(this))},_click:function(t){t.preventDefault(),e.api(this)},_ajaxSuccess:function(){var e=elBySel(".mainMenu .active .badge");e&&elRemove(e),elBySelAll(".articleList .newMessageBadge",void 0,elRemove)},_ajaxSetup:function(){return{data:{actionName:"markAllAsRead",className:"wcf\\data\\article\\ArticleAction"}}}}}),define("WoltLabSuite/Core/Ui/Article/Search",["Ajax","EventKey","Language","StringUtil","Dom/Util","Ui/Dialog"],function(e,t,i,n,a,r){"use strict";var o,s,l,c=null;return{open:function(e){o=e,r.open(this)},_search:function(t){t.preventDefault();var n=c.parentNode,a=c.value.trim();if(a.length<3)return void elInnerError(n,i.get("wcf.article.search.error.tooShort"));elInnerError(n,!1),e.api(this,{parameters:{searchString:a}})},_click:function(e){e.preventDefault(),o(elData(e.currentTarget,"article-id")),r.close(this)},_ajaxSuccess:function(e){for(var t,a="",r=0,o=e.returnValues.length;r<o;r++)t=e.returnValues[r],a+='<li><div class="containerHeadline pointer" data-article-id="'+t.articleID+'"><h3>'+n.escapeHTML(t.name)+"</h3><small>"+n.escapeHTML(t.displayLink)+"</small></div></li>";l.innerHTML=a,window[a?"elShow":"elHide"](s),a?elBySelAll(".containerHeadline",l,function(e){e.addEventListener(WCF_CLICK_EVENT,this._click.bind(this))}.bind(this)):elInnerError(c.parentNode,i.get("wcf.article.search.error.noResults"))},_ajaxSetup:function(){return{data:{actionName:"search",className:"wcf\\data\\article\\ArticleAction"}}},_dialogSetup:function(){return{id:"wcfUiArticleSearch",options:{onSetup:function(){var e=this._search.bind(this);c=elById("wcfUiArticleSearchInput"),c.addEventListener("keydown",function(i){t.Enter(i)&&e(i)}),c.nextElementSibling.addEventListener(WCF_CLICK_EVENT,e),s=elById("wcfUiArticleSearchResultContainer"),l=elById("wcfUiArticleSearchResultList")}.bind(this),onShow:function(){c.focus()},title:i.get("wcf.article.search")},source:'<div class="section"><dl><dt><label for="wcfUiArticleSearchInput">'+i.get("wcf.article.search.name")+'</label></dt><dd><div class="inputAddon"><input type="text" id="wcfUiArticleSearchInput" class="long"><a href="#" class="inputSuffix"><span class="icon icon16 fa-search"></span></a></div></dd></dl></div><section id="wcfUiArticleSearchResultContainer" class="section" style="display: none;"><header class="sectionHeader"><h2 class="sectionTitle">'+i.get("wcf.article.search.results")+'</h2></header><ol id="wcfUiArticleSearchResultList" class="containerList"></ol></section>'}}}}),define("WoltLabSuite/Core/Ui/Color/Picker",["Core"],function(e){"use strict";function t(e,t){this.init(e,t)}var i=function(e,t){if("object"==typeof window.WCF&&"function"==typeof window.WCF.ColorPicker)return(i=function(e,t){var i=new window.WCF.ColorPicker(e);return"function"==typeof t.callbackSubmit&&i.setCallbackSubmit(t.callbackSubmit),i})(e,t);0===n.length&&(window.__wcf_bc_colorPickerInit=function(){n.forEach(function(e){i(e[0],e[1])}),window.__wcf_bc_colorPickerInit=void 0,n=[]}),n.push([e,t])},n=[];return t.prototype={init:function(t,n){if(!(t instanceof Element))throw new TypeError("Expected a valid DOM element, use `UiColorPicker.fromSelector()` if you want to use a CSS selector.");this._options=e.extend({callbackSubmit:null},n),i(t,n)}},t.fromSelector=function(e){elBySelAll(e,void 0,function(e){new t(e)})},t}),define("WoltLabSuite/Core/Ui/Comment/Add",["Ajax","Core","EventHandler","Language","Dom/ChangeListener","Dom/Util","Dom/Traverse","Ui/Dialog","Ui/Notification","WoltLabSuite/Core/Ui/Scroll","EventKey","User","WoltLabSuite/Core/Controller/Captcha"],function(e,t,i,n,a,r,o,s,l,c,d,u,h){"use strict";function p(e){this.init(e)}return p.prototype={init:function(e){this._container=e,this._content=elBySel(".jsOuterEditorContainer",this._container),this._textarea=elBySel(".wysiwygTextarea",this._container),this._editor=null,this._loadingOverlay=null,this._content.addEventListener(WCF_CLICK_EVENT,function(e){this._content.classList.contains("collapsed")&&(e.preventDefault(),this._content.classList.remove("collapsed"),this._focusEditor())}.bind(this)),elBySel('button[data-type="save"]',this._container).addEventListener(WCF_CLICK_EVENT,this._submit.bind(this))},_focusEditor:function(){c.element(this._container,function(){window.jQuery(this._textarea).redactor("WoltLabCaret.endOfEditor")}.bind(this))},_submitGuestDialog:function(e){if("keypress"!==e.type||d.Enter(e)){var i=elBySel("input[name=username]",e.currentTarget.closest(".dialogContent"));if(""===i.value)return elInnerError(i,n.get("wcf.global.form.error.empty")),void i.closest("dl").classList.add("formError");var a={parameters:{data:{username:i.value}}};if(h.has("commentAdd")){var r=h.getData("commentAdd");r instanceof Promise?r.then(function(e){a=t.extend(a,e),this._submit(void 0,a)}.bind(this)):(a=t.extend(a,r),this._submit(void 0,a))}else this._submit(void 0,a)}},_submit:function(n,a){if(n&&n.preventDefault(),this._validate()){this._showLoadingOverlay();var r=this._getParameters();i.fire("com.woltlab.wcf.redactor2","submit_text",r.data),u.userId||a||(r.requireGuestDialog=!0),e.api(this,t.extend({parameters:r},a))}},_getParameters:function(){var e=this._container.closest(".commentList");return{data:{message:this._getEditor().code.get(),objectID:~~elData(e,"object-id"),objectTypeID:~~elData(e,"object-type-id")}}},_validate:function(){if(elBySelAll(".innerError",this._container,elRemove),this._getEditor().utils.isEmpty())return this.throwError(this._textarea,n.get("wcf.global.form.error.empty")),!1;var e={api:this,editor:this._getEditor(),message:this._getEditor().code.get(),valid:!0};return i.fire("com.woltlab.wcf.redactor2","validate_text",e),!1!==e.valid},throwError:function(e,t){elInnerError(e,"empty"===t?n.get("wcf.global.form.error.empty"):t)},_showLoadingOverlay:function(){null===this._loadingOverlay&&(this._loadingOverlay=elCreate("div"),this._loadingOverlay.className="commentLoadingOverlay",this._loadingOverlay.innerHTML='<span class="icon icon96 fa-spinner"></span>'),this._content.classList.add("loading"),this._content.appendChild(this._loadingOverlay)},_hideLoadingOverlay:function(){this._content.classList.remove("loading");var e=elBySel(".commentLoadingOverlay",this._content);null!==e&&e.parentNode.removeChild(e)},_reset:function(){this._getEditor().code.set("<p>​</p>"),i.fire("com.woltlab.wcf.redactor2","reset_text"),document.activeElement&&document.activeElement.blur(),this._content.classList.add("collapsed")},_handleError:function(e){this.throwError(this._textarea,e.returnValues.errorType)},_getEditor:function(){if(null===this._editor){if("function"!=typeof window.jQuery)throw new Error("Unable to access editor, jQuery has not been loaded yet.");this._editor=window.jQuery(this._textarea).data("redactor")}return this._editor},_insertMessage:function(e){return r.insertHtml(e.returnValues.template,this._container,"after"),l.show(n.get("wcf.global.success.add")),a.trigger(),this._container.nextElementSibling},_ajaxSuccess:function(e){if(!u.userId&&e.returnValues.guestDialog){s.openStatic("jsDialogGuestComment",e.returnValues.guestDialog,{closable:!1,onClose:function(){h.has("commentAdd")&&h.delete("commentAdd")},title:n.get("wcf.global.confirmation.title")});var t=s.getDialog("jsDialogGuestComment");elBySel("input[type=submit]",t.content).addEventListener(WCF_CLICK_EVENT,this._submitGuestDialog.bind(this)),elBySel('button[data-type="cancel"]',t.content).addEventListener(WCF_CLICK_EVENT,this._cancelGuestDialog.bind(this)),elBySel("input[type=text]",t.content).addEventListener("keypress",this._submitGuestDialog.bind(this))}else{var i=this._insertMessage(e);u.userId||s.close("jsDialogGuestComment"),this._reset(),this._hideLoadingOverlay(),window.setTimeout(function(){c.element(i)}.bind(this),100)}},_ajaxFailure:function(e){return this._hideLoadingOverlay(),null===e||void 0===e.returnValues||void 0===e.returnValues.errorType||(this._handleError(e),!1)},_ajaxSetup:function(){return{data:{actionName:"addComment",className:"wcf\\data\\comment\\CommentAction"},silent:!0}},_cancelGuestDialog:function(){s.close("jsDialogGuestComment"),this._hideLoadingOverlay()}},p}),define("WoltLabSuite/Core/Ui/Comment/Edit",["Ajax","Core","Dictionary","Environment","EventHandler","Language","List","Dom/ChangeListener","Dom/Traverse","Dom/Util","Ui/Notification","Ui/ReusableDropdown","WoltLabSuite/Core/Ui/Scroll"],function(e,t,i,n,a,r,o,s,l,c,d,u,h){"use strict";function p(e){this.init(e)}return p.prototype={init:function(e){this._activeElement=null,this._callbackClick=null,this._comments=new o,this._container=e,this._editorContainer=null,this.rebuild(),s.add("Ui/Comment/Edit_"+c.identify(this._container),this.rebuild.bind(this))},rebuild:function(){elBySelAll(".comment",this._container,function(e){if(!this._comments.has(e)){if(elDataBool(e,"can-edit")){var t=elBySel(".jsCommentEditButton",e);null!==t&&(null===this._callbackClick&&(this._callbackClick=this._click.bind(this)),t.addEventListener(WCF_CLICK_EVENT,this._callbackClick))}this._comments.add(e)}}.bind(this))},_click:function(t){t.preventDefault(),null===this._activeElement?(this._activeElement=t.currentTarget.closest(".comment"),this._prepare(),e.api(this,{actionName:"beginEdit",objectIDs:[this._getObjectId(this._activeElement)]})):d.show("wcf.message.error.editorAlreadyInUse",null,"warning")},_prepare:function(){this._editorContainer=elCreate("div"),this._editorContainer.className="commentEditorContainer",this._editorContainer.innerHTML='<span class="icon icon48 fa-spinner"></span>';var e=elBySel(".commentContentContainer",this._activeElement);e.insertBefore(this._editorContainer,e.firstChild)},_showEditor:function(e){var t=this._getEditorId(),i=elBySel(".icon",this._editorContainer);elRemove(i);var r=elCreate("div");r.className="editorContainer",c.setInnerHtml(r,e.returnValues.template),this._editorContainer.appendChild(r);var o=elBySel(".formSubmit",r);elBySel('button[data-type="save"]',o).addEventListener(WCF_CLICK_EVENT,this._save.bind(this)),elBySel('button[data-type="cancel"]',o).addEventListener(WCF_CLICK_EVENT,this._restoreMessage.bind(this)),a.add("com.woltlab.wcf.redactor","submitEditor_"+t,function(e){e.cancel=!0,this._save()}.bind(this));var s=elById(t);"redactor"===n.editor()?window.setTimeout(function(){h.element(this._activeElement)}.bind(this),250):s.focus()},_restoreMessage:function(){this._destroyEditor(),elRemove(this._editorContainer),this._activeElement=null},_save:function(){var t={data:{message:""}},i=this._getEditorId();a.fire("com.woltlab.wcf.redactor2","getText_"+i,t.data),this._validate(t)&&(a.fire("com.woltlab.wcf.redactor2","submit_"+i,t),e.api(this,{actionName:"save",objectIDs:[this._getObjectId(this._activeElement)],parameters:t}),this._hideEditor())},_validate:function(e){elBySelAll(".innerError",this._activeElement,elRemove);var t=elById(this._getEditorId());if(window.jQuery(t).data("redactor").utils.isEmpty())return this.throwError(t,r.get("wcf.global.form.error.empty")),!1;var i={api:this,parameters:e,valid:!0};return a.fire("com.woltlab.wcf.redactor2","validate_"+this._getEditorId(),i),!1!==i.valid},throwError:function(e,t){elInnerError(e,t)},_showMessage:function(e){c.setInnerHtml(elBySel(".commentContent .userMessage",this._editorContainer.parentNode),e.returnValues.message),this._restoreMessage(),d.show()},_hideEditor:function(){elHide(elBySel(".editorContainer",this._editorContainer));var e=elCreate("span");e.className="icon icon48 fa-spinner",this._editorContainer.appendChild(e)},_restoreEditor:function(){var e=elBySel(".fa-spinner",this._editorContainer);elRemove(e);var t=elBySel(".editorContainer",this._editorContainer);null!==t&&elShow(t)},_destroyEditor:function(){a.fire("com.woltlab.wcf.redactor2","autosaveDestroy_"+this._getEditorId()),a.fire("com.woltlab.wcf.redactor2","destroy_"+this._getEditorId())},_getEditorId:function(){return"commentEditor"+this._getObjectId(this._activeElement)},_getObjectId:function(e){return~~elData(e,"object-id")},_ajaxFailure:function(e){var t=elBySel(".redactor-layer",this._editorContainer);return null===t?(this._restoreMessage(),!0):(this._restoreEditor(),!e||void 0===e.returnValues||void 0===e.returnValues.errorType||(elInnerError(t,e.returnValues.errorType),!1))},_ajaxSuccess:function(e){switch(e.actionName){case"beginEdit":this._showEditor(e);break;case"save":this._showMessage(e)}},_ajaxSetup:function(){return{data:{className:"wcf\\data\\comment\\CommentAction",parameters:{data:{objectTypeID:~~elData(this._container,"object-type-id")}}},silent:!0}}},p}),define("WoltLabSuite/Core/Ui/Dropdown/Builder",["Core","Ui/SimpleDropdown"],function(e,t){"use strict";function i(e){if(!(e instanceof HTMLUListElement))throw new TypeError("Expected a reference to an <ul> element.");if(!e.classList.contains("dropdownMenu"))throw new Error("List does not appear to be a dropdown menu.")}function n(t){var i=elCreate("li");if("divider"===t)return i.className="dropdownDivider",i;"string"==typeof t.identifier&&elData(i,"identifier",t.identifier);var n=elCreate("a");if(n.href="string"==typeof t.href?t.href:"#","function"==typeof t.callback)n.addEventListener(WCF_CLICK_EVENT,function(e){e.preventDefault(),t.callback(n)});else if("#"===n.getAttribute("href"))throw new Error("Expected either a `href` value or a `callback`.");if(t.hasOwnProperty("attributes")&&e.isPlainObject(t.attributes))for(var r in t.attributes)t.attributes.hasOwnProperty(r)&&elData(n,r,t.attributes[r]);if(i.appendChild(n),void 0!==t.icon&&e.isPlainObject(t.icon)){if("string"!=typeof t.icon.name)throw new TypeError("Expected a valid icon name.");var o=16;"number"==typeof t.icon.size&&-1!==a.indexOf(~~t.icon.size)&&(o=~~t.icon.size);var s=elCreate("span");s.className="icon icon"+o+" fa-"+t.icon.name,n.appendChild(s)}var l="string"==typeof t.label?t.label.trim():"",c="string"==typeof t.labelHtml?t.labelHtml.trim():"";if(""===l&&""===c)throw new TypeError("Expected either a label or a `labelHtml`.");var d=elCreate("span");return d[l?"textContent":"innerHTML"]=l||c,n.appendChild(document.createTextNode(" ")),n.appendChild(d),i}var a=[16,24,32,48,64,96,144];return{create:function(e,t){var i=elCreate("ul");return i.className="dropdownMenu","string"==typeof t&&elData(i,"identifier",t),Array.isArray(e)&&e.length>0&&this.appendItems(i,e),i},buildItem:function(e){return n(e)},appendItem:function(e,t){i(e),e.appendChild(n(t))},appendItems:function(e,t){if(i(e),!Array.isArray(t))throw new TypeError("Expected an array of items.");var a=t.length;if(0===a)throw new Error("Expected a non-empty list of items.");if(1===a)this.appendItem(e,t[0]);else{for(var r=document.createDocumentFragment(),o=0;o<a;o++)r.appendChild(n(t[o]));e.appendChild(r)}},setItems:function(e,t){i(e),e.innerHTML="",this.appendItems(e,t)},attach:function(e,n){i(e),t.initFragment(n,e),n.addEventListener(WCF_CLICK_EVENT,function(e){e.preventDefault(),e.stopPropagation(),t.toggleDropdown(n.id)})},divider:function(){return"divider"}}}),define("WoltLabSuite/Core/Ui/File/Delete",["Ajax","Core","Dom/ChangeListener","Language","Dom/Util","Dom/Traverse","Dictionary"],function(e,t,i,n,a,r,o){"use strict";function s(e,t,i,n){if(this._isSingleImagePreview=i,this._uploadHandler=n,this._buttonContainer=elById(e),null===this._buttonContainer)throw new Error("Element id '"+e+"' is unknown.");if(this._target=elById(t),null===t)throw new Error("Element id '"+t+"' is unknown.");if(this._containers=new o,this._internalId=elData(this._target,"internal-id"),!this._internalId)throw new Error("InternalId is unknown.");this.rebuild()}return s.prototype={_createButtons:function(){for(var e,t,n,a=elBySelAll("li.uploadedFile",this._target),r=!1,o=0,s=a.length;o<s;o++)e=a[o],n=elData(e,"unique-file-id"),this._containers.has(n)||(t={uniqueFileId:n,element:e},this._containers.set(n,t),this._initDeleteButton(e,t),r=!0);r&&i.trigger()},_initDeleteButton:function(e,t){var i=elBySel(".buttonGroup",e);if(null===i)throw new Error("Button group in '"+targetId+"' is unknown.");var a=elCreate("li"),r=elCreate("span");r.classList="button jsDeleteButton small",r.textContent=n.get("wcf.global.button.delete"),a.appendChild(r),i.appendChild(a),
+a.addEventListener(WCF_CLICK_EVENT,this._delete.bind(this,t.uniqueFileId))},_delete:function(t){e.api(this,{uniqueFileId:t,internalId:this._internalId})},rebuild:function(){if(this._isSingleImagePreview){var e=elBySel("img",this._target);if(null!==e){var t=elData(e,"unique-file-id");if(!this._containers.has(t)){var i={uniqueFileId:t,element:e};this._containers.set(t,i),this._deleteButton=elCreate("p"),this._deleteButton.className="button deleteButton";var a=elCreate("span");a.textContent=n.get("wcf.global.button.delete"),this._deleteButton.appendChild(a),this._buttonContainer.appendChild(this._deleteButton),this._deleteButton.addEventListener(WCF_CLICK_EVENT,this._delete.bind(this,i.uniqueFileId))}}}else this._createButtons()},_ajaxSuccess:function(e){elRemove(this._containers.get(e.uniqueFileId).element),this._isSingleImagePreview&&(elRemove(this._deleteButton),this._deleteButton=null),this._uploadHandler.checkMaxFiles()},_ajaxSetup:function(){return{url:"index.php?ajax-file-delete/&t="+SECURITY_TOKEN}}},s}),define("WoltLabSuite/Core/Ui/File/Upload",["Core","Language","Dom/Util","WoltLabSuite/Core/Ui/File/Delete","Upload"],function(e,t,i,n,a){"use strict";function r(t,i,a){if(a=a||{},void 0===a.internalId)throw new Error("Missing internal id.");if(this._options=e.extend({name:"__files[]",singleFileRequests:!1,url:"index.php?ajax-file-upload/&t="+SECURITY_TOKEN,imagePreview:!1,maxFiles:null},a),this._options.multiple=null===this._options.maxFiles||this._options.maxFiles>1,this._options.url=this._options.url,0===this._options.url.indexOf("index.php")&&(this._options.url=WSC_API_URL+this._options.url),this._buttonContainer=elById(t),null===this._buttonContainer)throw new Error("Element id '"+t+"' is unknown.");if(this._target=elById(i),null===i)throw new Error("Element id '"+i+"' is unknown.");if(a.multiple&&"UL"!==this._target.nodeName&&"OL"!==this._target.nodeName)throw new Error("Target element has to be list or table body if uploading multiple files is supported.");this._fileElements=[],this._internalFileId=0,this._multiFileUploadIds=[],this._createButton(),this.checkMaxFiles(),this._deleteHandler=new n(t,i,this._options.imagePreview,this)}return e.inherit(r,a,{_createFileElement:function(e){var t=r._super.prototype._createFileElement.call(this,e);t.classList.add("box64","uploadedFile");var i=elBySel("progress",t),n=elCreate("span");n.classList="icon icon64 fa-spinner";var a=t.textContent;t.textContent="",t.append(n);var o=elCreate("div"),s=elCreate("p");s.textContent=a;var l=elCreate("small");l.appendChild(i),o.appendChild(s),o.appendChild(l);var c=elCreate("div");c.appendChild(o);var d=elCreate("ul");return d.classList="buttonGroup",c.appendChild(d),t.append(c),t},_failure:function(e,n,a,r,o){for(var s=0,l=this._fileElements[e].length;s<l;s++){this._fileElements[e][s].classList.add("uploadFailed"),elBySel("small",this._fileElements[e][s]).innerHTML="";var c=elBySel(".icon",this._fileElements[e][s]);c.classList.remove("fa-spinner"),c.classList.add("fa-ban");var d=elCreate("span");d.classList="innerError",d.textContent=t.get("wcf.upload.error.uploadFailed"),i.insertAfter(d,elBySel("small",this._fileElements[e][s]))}throw new Error("Upload failed: "+n.message)},_upload:function(e,t,i){var n=elBySel("small.innerError:not(.innerFileError)",this._buttonContainer.parentNode);return n&&elRemove(n),r._super.prototype._upload.call(this,e,t,i)},_success:function(e,t,n,a,r){for(var o=0,s=this._fileElements[e].length;o<s;o++)if(void 0!==t.files[o])if(this._options.imagePreview){if(null===t.files[o].image)throw new Error("Expect image for uploaded file. None given.");if(elRemove(this._fileElements[e][o]),null!==elBySel("img.previewImage",this._target))elBySel("img.previewImage",this._target).setAttribute("src",t.files[o].image);else{var l=elCreate("img");l.classList.add("previewImage"),l.setAttribute("src",t.files[o].image),l.setAttribute("style","max-width: 100%;"),elData(l,"unique-file-id",t.files[o].uniqueFileId),this._target.appendChild(l)}}else{elData(this._fileElements[e][o],"unique-file-id",t.files[o].uniqueFileId),elBySel("small",this._fileElements[e][o]).textContent=t.files[o].filesize;var c=elBySel(".icon",this._fileElements[e][o]);c.classList.remove("fa-spinner"),c.classList.add("fa-"+t.files[o].icon)}else{if(void 0===t.error[o])throw new Error("Unknown uploaded file for uploadId "+e+".");this._fileElements[e][o].classList.add("uploadFailed"),elBySel("small",this._fileElements[e][o]).innerHTML="";var c=elBySel(".icon",this._fileElements[e][o]);if(c.classList.remove("fa-spinner"),c.classList.add("fa-ban"),null===elBySel(".innerError",this._fileElements[e][o])){var d=elCreate("span");d.classList="innerError",d.textContent=t.error[o].errorMessage,i.insertAfter(d,elBySel("small",this._fileElements[e][o]))}else elBySel(".innerError",this._fileElements[e][o]).textContent=t.error[o].errorMessage}this._deleteHandler.rebuild(),this.checkMaxFiles()},_getFormData:function(){return{internalId:this._options.internalId}},validateUpload:function(e){if(null===this._options.maxFiles||e.length+this.countFiles()<=this._options.maxFiles)return!0;var n=elBySel("small.innerError:not(.innerFileError)",this._buttonContainer.parentNode);return null===n&&(n=elCreate("small"),n.classList="innerError",i.insertAfter(n,this._buttonContainer)),n.textContent=t.get("wcf.upload.error.reachedRemainingLimit",{maxFiles:this._options.maxFiles-this.countFiles()}),!1},countFiles:function(){return this._options.imagePreview?null!==elBySel("img",this._target)?1:0:this._target.childElementCount},checkMaxFiles:function(){null!==this._options.maxFiles&&this.countFiles()>=this._options.maxFiles?elHide(this._button):elShow(this._button)}}),r}),define("WoltLabSuite/Core/Ui/ItemList/Filter",["Core","EventKey","Language","List","StringUtil","Dom/Util","Ui/SimpleDropdown"],function(e,t,i,n,a,r,o){"use strict";function s(e,t){this.init(e,t)}return s.prototype={init:function(n,a){this._value="",this._options=e.extend({callbackPrepareItem:void 0,enableVisibilityFilter:!0},a);var r=elById(n);if(null===r)throw new Error("Expected a valid element id, '"+n+"' does not match anything.");if(!r.classList.contains("scrollableCheckboxList")&&"function"!=typeof this._options.callbackPrepareItem)throw new Error("Filter only works with elements with the CSS class 'scrollableCheckboxList'.");elData(r,"filter","showAll");var o=elCreate("div");o.className="itemListFilter",r.parentNode.insertBefore(o,r),o.appendChild(r);var s=elCreate("div");s.className="inputAddon";var l=elCreate("input");l.className="long",l.type="text",l.placeholder=i.get("wcf.global.filter.placeholder"),l.addEventListener("keydown",function(e){t.Enter(e)&&e.preventDefault()}),l.addEventListener("keyup",this._keyup.bind(this));var c=elCreate("a");if(c.href="#",c.className="button inputSuffix jsTooltip",c.title=i.get("wcf.global.filter.button.clear"),c.innerHTML='<span class="icon icon16 fa-times"></span>',c.addEventListener("click",function(e){e.preventDefault(),this.reset()}.bind(this)),s.appendChild(l),s.appendChild(c),this._options.enableVisibilityFilter){var d=elCreate("a");d.href="#",d.className="button inputSuffix jsTooltip",d.title=i.get("wcf.global.filter.button.visibility"),d.innerHTML='<span class="icon icon16 fa-eye"></span>',d.addEventListener(WCF_CLICK_EVENT,this._toggleVisibility.bind(this)),s.appendChild(d)}o.appendChild(s),this._container=o,this._dropdown=null,this._dropdownId="",this._element=r,this._input=l,this._items=null,this._fragment=null},reset:function(){this._input.value="",this._keyup()},_buildItems:function(){this._items=new n;for(var e="function"==typeof this._options.callbackPrepareItem?this._options.callbackPrepareItem:this._prepareItem.bind(this),t=0,i=this._element.childElementCount;t<i;t++)this._items.add(e(this._element.children[t]))},_prepareItem:function(e){for(var t=e.children[0],i=t.textContent.trim(),n=t.children[0];n.nextSibling;)t.removeChild(n.nextSibling);t.appendChild(document.createTextNode(" "));var a=elCreate("span");return a.textContent=i,t.appendChild(a),{item:e,span:a,text:i}},_keyup:function(){var e=this._input.value.trim();if(this._value!==e){null===this._fragment&&(this._fragment=document.createDocumentFragment(),this._element.style.setProperty("height",this._element.offsetHeight+"px","")),this._fragment.appendChild(this._element),null===this._items&&this._buildItems();var t=new RegExp("("+a.escapeRegExp(e)+")","i"),n=""===e;this._items.forEach(function(i){""===e?(i.span.textContent=i.text,elShow(i.item)):t.test(i.text)?(i.span.innerHTML=i.text.replace(t,"<u>$1</u>"),elShow(i.item),n=!0):elHide(i.item)}),this._container.insertBefore(this._fragment.firstChild,this._container.firstChild),this._value=e,elInnerError(this._container,!n&&i.get("wcf.global.filter.error.noMatches"))}},_toggleVisibility:function(e){e.preventDefault(),e.stopPropagation();var t=e.currentTarget;if(null===this._dropdown){var n=elCreate("ul");n.className="dropdownMenu",["activeOnly","highlightActive","showAll"].forEach(function(e){var t=elCreate("a");elData(t,"type",e),t.href="#",t.textContent=i.get("wcf.global.filter.visibility."+e),t.addEventListener(WCF_CLICK_EVENT,this._setVisibility.bind(this));var a=elCreate("li");if(a.appendChild(t),"showAll"===e){a.className="active";var r=elCreate("li");r.className="dropdownDivider",n.appendChild(r)}n.appendChild(a)}.bind(this)),o.initFragment(t,n),this._setupVisibilityFilter(),this._dropdown=n,this._dropdownId=t.id}o.toggleDropdown(t.id,t)},_setupVisibilityFilter:function(){var e=this._element.nextSibling,t=this._element.parentNode,i=this._element.scrollTop;document.createDocumentFragment().appendChild(this._element),elBySelAll("li",this._element,function(e){var t=elBySel('input[type="checkbox"]',e);if(t)t.checked&&e.classList.add("active"),t.addEventListener("change",function(){e.classList[t.checked?"add":"remove"]("active")});else{var i=elBySel('input[type="radio"]',e);i&&(i.checked&&e.classList.add("active"),i.addEventListener("change",function(){elBySelAll("li",this._element,function(e){e.classList.remove("active")}),e.classList[i.checked?"add":"remove"]("active")}.bind(this)))}}.bind(this)),t.insertBefore(this._element,e),this._element.scrollTop=i},_setVisibility:function(e){e.preventDefault();var t=e.currentTarget,i=elData(t,"type");if(o.close(this._dropdownId),elData(this._element,"filter")!==i){elData(this._element,"filter",i),elBySel(".active",this._dropdown).classList.remove("active"),t.parentNode.classList.add("active");var n=elById(this._dropdownId);n.classList["showAll"===i?"remove":"add"]("active");var a=elBySel(".icon",n);a.classList["showAll"===i?"add":"remove"]("fa-eye"),a.classList["showAll"===i?"remove":"add"]("fa-eye-slash")}}},s}),define("WoltLabSuite/Core/Ui/ItemList/Static",["Core","Dictionary","Language","Dom/Traverse","EventKey","Ui/SimpleDropdown"],function(e,t,i,n,a,r){"use strict";var o="",s=new t,l=!1,c=null,d=null,u=null,h=null,p=null,f=null;return{init:function(t,i,a){var o=elById(t);if(null===o)throw new Error("Expected a valid element id, '"+t+"' is invalid.");if(s.has(t)){var l=s.get(t);for(var c in l)if(l.hasOwnProperty(c)){var d=l[c];d instanceof Element&&d.parentNode&&elRemove(d)}r.destroy(t),s.delete(t)}a=e.extend({maxItems:-1,maxLength:-1,isCSV:!1,callbackChange:null,callbackSubmit:null,submitFieldName:""},a);var u=n.parentByTag(o,"FORM");if(null!==u&&!1===a.isCSV){if(!a.submitFieldName.length&&"function"!=typeof a.callbackSubmit)throw new Error("Expected a valid function for option 'callbackSubmit', a non-empty value for option 'submitFieldName' or enabling the option 'submitFieldCSV'.");u.addEventListener("submit",function(){var e=this.getValues(t);if(a.submitFieldName.length)for(var i,n=0,r=e.length;n<r;n++)i=elCreate("input"),i.type="hidden",i.name=a.submitFieldName.replace(/{$objectId}/,e[n].objectId),i.value=e[n].value,u.appendChild(i);else a.callbackSubmit(u,e)}.bind(this))}this._setup();var h=this._createUI(o,a);if(s.set(t,{dropdownMenu:null,element:h.element,list:h.list,listItem:h.element.parentNode,options:a,shadow:h.shadow}),i=h.values.length?h.values:i,Array.isArray(i))for(var p,f=0,m=i.length;f<m;f++)p=i[f],"string"==typeof p&&(p={objectId:0,value:p}),this._addItem(t,p)},getValues:function(e){if(!s.has(e))throw new Error("Element id '"+e+"' is unknown.");var t=s.get(e),i=[];return elBySelAll(".item > span",t.list,function(e){i.push({objectId:~~elData(e,"object-id"),value:e.textContent})}),i},setValues:function(e,t){if(!s.has(e))throw new Error("Element id '"+e+"' is unknown.");var i,a,r=s.get(e),o=n.childrenByClass(r.list,"item");for(i=0,a=o.length;i<a;i++)this._removeItem(null,o[i],!0);for(i=0,a=t.length;i<a;i++)this._addItem(e,t[i])},_setup:function(){l||(l=!0,c=this._keyDown.bind(this),d=this._keyPress.bind(this),u=this._keyUp.bind(this),h=this._paste.bind(this),p=this._removeItem.bind(this),f=this._blur.bind(this))},_createUI:function(e,t){var i=elCreate("ol");i.className="inputItemList"+(e.disabled?" disabled":""),elData(i,"element-id",e.id),i.addEventListener(WCF_CLICK_EVENT,function(t){t.target===i&&e.focus()});var n=elCreate("li");n.className="input",i.appendChild(n),e.addEventListener("keydown",c),e.addEventListener("keypress",d),e.addEventListener("keyup",u),e.addEventListener("paste",h),e.addEventListener("blur",f),e.parentNode.insertBefore(i,e),n.appendChild(e),-1!==t.maxLength&&elAttr(e,"maxLength",t.maxLength);var a=null,r=[];if(t.isCSV){a=elCreate("input"),a.className="itemListInputShadow",a.type="hidden",a.name=e.name,e.removeAttribute("name"),i.parentNode.insertBefore(a,i);for(var o,s=e.value.split(","),l=0,p=s.length;l<p;l++)o=s[l].trim(),o.length&&r.push(o);if("TEXTAREA"===e.nodeName){var m=elCreate("input");m.type="text",e.parentNode.insertBefore(m,e),m.id=e.id,elRemove(e),e=m}}return{element:e,list:i,shadow:a,values:r}},_handleLimit:function(e){var t=s.get(e);-1!==t.options.maxItems&&(t.list.childElementCount-1<t.options.maxItems?t.element.disabled&&(t.element.disabled=!1,t.element.removeAttribute("placeholder")):t.element.disabled||(t.element.disabled=!0,elAttr(t.element,"placeholder",i.get("wcf.global.form.input.maxItems"))))},_keyDown:function(e){var t=e.currentTarget,i=t.parentNode.previousElementSibling;o=t.id,8===e.keyCode?0===t.value.length&&null!==i&&(i.classList.contains("active")?this._removeItem(null,i):i.classList.add("active")):27===e.keyCode&&null!==i&&i.classList.contains("active")&&i.classList.remove("active")},_keyPress:function(e){if(a.Enter(e)||a.Comma(e)){e.preventDefault();var t=e.currentTarget.value.trim();t.length&&this._addItem(e.currentTarget.id,{objectId:0,value:t})}},_paste:function(e){var t="";t="object"==typeof window.clipboardData?window.clipboardData.getData("Text"):e.clipboardData.getData("text/plain"),t.split(/,/).forEach(function(t){t=t.trim(),0!==t.length&&this._addItem(e.currentTarget.id,{objectId:0,value:t})}.bind(this)),e.preventDefault()},_keyUp:function(e){var t=e.currentTarget;if(t.value.length>0){var i=t.parentNode.previousElementSibling;null!==i&&i.classList.remove("active")}},_addItem:function(e,t){var i=s.get(e),n=elCreate("li");n.className="item";var a=elCreate("span");if(a.className="content",elData(a,"object-id",t.objectId),a.textContent=t.value,n.appendChild(a),!i.element.disabled){var r=elCreate("a");r.className="icon icon16 fa-times",r.addEventListener(WCF_CLICK_EVENT,p),n.appendChild(r)}i.list.insertBefore(n,i.listItem),i.element.value="",i.element.disabled||this._handleLimit(e);var o=this._syncShadow(i);"function"==typeof i.options.callbackChange&&(null===o&&(o=this.getValues(e)),i.options.callbackChange(e,o))},_removeItem:function(e,t,i){t=null===e?t:e.currentTarget.parentNode;var n=t.parentNode,a=elData(n,"element-id"),r=s.get(a);n.removeChild(t),i||r.element.focus(),this._handleLimit(a);var o=this._syncShadow(r);"function"==typeof r.options.callbackChange&&(null===o&&(o=this.getValues(a)),r.options.callbackChange(a,o))},_syncShadow:function(e){if(!e.options.isCSV)return null;for(var t="",i=this.getValues(e.element.id),n=0,a=i.length;n<a;n++)t+=(t.length?",":"")+i[n].value;return e.shadow.value=t,i},_blur:function(e){var t=(s.get(e.currentTarget.id),e.currentTarget);window.setTimeout(function(){var e=t.value.trim();e.length&&this._addItem(t.id,{objectId:0,value:e})}.bind(this),100)}}}),define("WoltLabSuite/Core/Ui/ItemList/User",["WoltLabSuite/Core/Ui/ItemList"],function(e){"use strict";return{_shadowGroups:null,init:function(t,i){this._shadowGroups=null,e.init(t,[],{ajax:{className:"wcf\\data\\user\\UserAction",parameters:{data:{includeUserGroups:~~i.includeUserGroups,restrictUserGroupIDs:Array.isArray(i.restrictUserGroupIDs)?i.restrictUserGroupIDs:[]}}},callbackChange:"function"==typeof i.callbackChange?i.callbackChange:null,callbackSyncShadow:i.csvPerType?this._syncShadow.bind(this):null,callbackSetupValues:"function"==typeof i.callbackSetupValues?i.callbackSetupValues:null,excludedSearchValues:Array.isArray(i.excludedSearchValues)?i.excludedSearchValues:[],isCSV:!0,maxItems:~~i.maxItems||-1,restricted:!0})},getValues:function(t){return e.getValues(t)},_syncShadow:function(e){var t=this.getValues(e.element.id),i=[],n=[];return t.forEach(function(e){e.type&&"group"===e.type?n.push(e.objectId):i.push(e.value)}),e.shadow.value=i.join(","),this._shadowGroups||(this._shadowGroups=elCreate("input"),this._shadowGroups.type="hidden",this._shadowGroups.name=e.shadow.name+"GroupIDs",e.shadow.parentNode.insertBefore(this._shadowGroups,e.shadow)),this._shadowGroups.value=n.join(","),t}}}),define("WoltLabSuite/Core/Ui/User/List",["Ajax","Core","Dictionary","Dom/Util","Ui/Dialog","WoltLabSuite/Core/Ui/Pagination"],function(e,t,i,n,a,r){"use strict";function o(e){this.init(e)}return o.prototype={init:function(e){this._cache=new i,this._pageCount=0,this._pageNo=1,this._options=t.extend({className:"",dialogTitle:"",parameters:{}},e)},open:function(){this._pageNo=1,this._showPage()},_showPage:function(t){if("number"==typeof t&&(this._pageNo=~~t),0!==this._pageCount&&(this._pageNo<1||this._pageNo>this._pageCount))throw new RangeError("pageNo must be between 1 and "+this._pageCount+" ("+this._pageNo+" given).");if(this._cache.has(this._pageNo)){var i=a.open(this,this._cache.get(this._pageNo));if(this._pageCount>1){var n=elBySel(".jsPagination",i.content);null!==n&&new r(n,{activePage:this._pageNo,maxPage:this._pageCount,callbackSwitch:this._showPage.bind(this)});var o=i.content.parentNode;o.scrollTop>0&&(o.scrollTop=0)}}else this._options.parameters.pageNo=this._pageNo,e.api(this,{parameters:this._options.parameters})},_ajaxSuccess:function(e){void 0!==e.returnValues.pageCount&&(this._pageCount=~~e.returnValues.pageCount),this._cache.set(this._pageNo,e.returnValues.template),this._showPage()},_ajaxSetup:function(){return{data:{actionName:"getGroupedUserList",className:this._options.className,interfaceName:"wcf\\data\\IGroupedUserListAction"}}},_dialogSetup:function(){return{id:n.getUniqueId(),options:{title:this._options.dialogTitle},source:null}}},o}),define("WoltLabSuite/Core/Ui/Reaction/CountButtons",["Ajax","Core","Dictionary","Language","ObjectMap","StringUtil","Dom/ChangeListener","Dom/Util","Ui/Dialog"],function(e,t,i,n,a,r,o,s,l){"use strict";function c(e,t){this.init(e,t)}return c.prototype={init:function(e,n){if(""===n.containerSelector)throw new Error("[WoltLabSuite/Core/Ui/Reaction/CountButtons] Expected a non-empty string for option 'containerSelector'.");this._containers=new i,this._objects=new i,this._objectType=e,this._options=t.extend({summaryListSelector:".reactionSummaryList",containerSelector:"",isSingleItem:!1,parameters:{data:{}}},n),this.initContainers(n,e),o.add("WoltLabSuite/Core/Ui/Reaction/CountButtons-"+e,this.initContainers.bind(this))},initContainers:function(){for(var e,t,i=elBySelAll(this._options.containerSelector),n=!1,a=0,r=i.length;a<r;a++)if(e=i[a],!this._containers.has(s.identify(e))){if(t={reactButton:null,summary:null,objectId:~~elData(e,"object-id"),element:e},this._containers.set(s.identify(e),t),this._initReactionCountButtons(e,t),this._objects.has(~~elData(e,"object-id")))var l=this._objects.get(~~elData(e,"object-id"));else var l=[];l.push(t),this._objects.set(~~elData(e,"object-id"),l),n=!0}n&&o.trigger()},updateCountButtons:function(e,t){var i=!1;this._objects.get(e).forEach(function(n){for(var a=elBySel(this._options.summaryListSelector,n.element),o={},s=elBySelAll("li",a),l=0,c=s.length;l<c;l++)void 0!==t[elData(s[l],"reaction-type-id")]?o[elData(s[l],"reaction-type-id")]=s[l]:elRemove(s[l]);Object.keys(t).forEach(function(n){if(void 0!==o[n]){elBySel(".reactionCount",o[n]).innerHTML=r.shortUnit(t[n])}else if(void 0!==REACTION_TYPES[n]){var s=elCreate("li");s.className="reactCountButton",elData(s,"reaction-type-id",n);var l=elCreate("span");l.className="reactionCount",l.innerHTML=r.shortUnit(t[n]),s.appendChild(l),s.innerHTML=s.innerHTML+REACTION_TYPES[n].renderedIcon,a.appendChild(s),this._initReactionCountButton(s,e),i=!0}},this)}.bind(this)),i&&o.trigger()},_initReactionCountButtons:function(e,t){if(this._options.isSingleItem)var i=elBySel(this._options.summaryListSelector);else var i=elBySel(this._options.summaryListSelector,e);if(null!==i)for(var n=elBySelAll("li",i),a=0,r=n.length;a<r;a++)this._initReactionCountButton(n[a],t.objectId)},_initReactionCountButton:function(e,t){e.addEventListener(WCF_CLICK_EVENT,this._showReactionOverlay.bind(this,t))},_showReactionOverlay:function(e){this._currentObjectId=e,this._showOverlay()},_showOverlay:function(){this._options.parameters.data.containerID=this._objectType+"-"+this._currentObjectId,this._options.parameters.data.objectID=this._currentObjectId,this._options.parameters.data.objectType=this._objectType,e.api(this,{parameters:this._options.parameters})},_ajaxSuccess:function(e){l.open(this,e.returnValues.template),l.setTitle("userReactionOverlay-"+this._objectType,e.returnValues.title)},_ajaxSetup:function(){return{data:{actionName:"getReactionDetails",className:"\\wcf\\data\\reaction\\ReactionAction"}}},_dialogSetup:function(){return{id:"userReactionOverlay-"+this._objectType,options:{title:""},source:null}}},c}),define("WoltLabSuite/Core/Ui/Reaction/Handler",["Ajax","Core","Dictionary","Language","ObjectMap","StringUtil","Dom/ChangeListener","Dom/Util","Ui/Dialog","WoltLabSuite/Core/Ui/User/List","User","WoltLabSuite/Core/Ui/Reaction/CountButtons","Ui/Alignment","Ui/CloseOverlay","Ui/Screen"],function(e,t,i,n,a,r,o,s,l,c,d,u,h,p,f){"use strict";function m(e,t){this.init(e,t)}return m.prototype={init:function(e,n){if(""===n.containerSelector)throw new Error("[WoltLabSuite/Core/Ui/Reaction/Handler] Expected a non-empty string for option 'containerSelector'.");this._containers=new i,this._details=new a,this._objectType=e,this._cache=new i,this._objects=new i,this._popoverCurrentObjectId=0,this._popover=null,this._options=t.extend({buttonSelector:".reactButton",containerSelector:"",isButtonGroupNavigation:!1,isSingleItem:!1,parameters:{data:{}}},n),this.initReactButtons(n,e),this.countButtons=new u(this._objectType,this._options),o.add("WoltLabSuite/Core/Ui/Reaction/Handler-"+e,this.initReactButtons.bind(this)),p.add("WoltLabSuite/Core/Ui/Reaction/Handler",this._closePopover.bind(this))},initReactButtons:function(){for(var e,t,i=elBySelAll(this._options.containerSelector),n=!1,a=0,r=i.length;a<r;a++)if(e=i[a],!this._containers.has(s.identify(e))){if(t={reactButton:null,objectId:~~elData(e,"object-id"),element:e},this._containers.set(s.identify(e),t),this._initReactButton(e,t),this._objects.has(~~elData(e,"object-id")))var l=this._objects.get(~~elData(e,"object-id"));else var l=[];l.push(t),this._objects.set(~~elData(e,"object-id"),l),n=!0}n&&o.trigger()},_initReactButton:function(e,t){if(this._options.isSingleItem?t.reactButton=elBySel(this._options.buttonSelector):t.reactButton=elBySel(this._options.buttonSelector,e),null!==t.reactButton&&0!==t.reactButton.length){if(1===Object.keys(REACTION_TYPES).length){var i=REACTION_TYPES[Object.keys(REACTION_TYPES)[0]];t.reactButton.title=i.title;elBySel(".invisible",t.reactButton).innerText=i.title}t.reactButton.closest(".messageFooterGroup > .jsMobileNavigation")&&f.on("screen-sm-down",{match:this._enableMobileView.bind(this,t.reactButton,t.objectId),unmatch:this._disableMobileView.bind(this,t.reactButton,t.objectId),setup:this._setupMobileView.bind(this,t.reactButton,t.objectId)}),t.reactButton.addEventListener(WCF_CLICK_EVENT,this._toggleReactPopover.bind(this,t.objectId,t.reactButton))}},_enableMobileView:function(e){var t=e.closest(".messageFooterGroup");elShow(elBySel(".mobileReactButton",t))},_disableMobileView:function(e){var t=e.closest(".messageFooterGroup");elHide(elBySel(".mobileReactButton",t))},_setupMobileView:function(e,t){var i=e.closest(".messageFooterGroup"),n=elCreate("button");n.classList="mobileReactButton",n.innerHTML=e.innerHTML,n.addEventListener(WCF_CLICK_EVENT,this._toggleReactPopover.bind(this,t,n)),i.appendChild(n)},_updateReactButton:function(e,t){this._objects.get(e).forEach(function(e){t?(e.reactButton.classList.add("active"),elData(e.reactButton,"reaction-type-id",t)):(elData(e.reactButton,"reaction-type-id",0),e.reactButton.classList.remove("active"))})},_markReactionAsActive:function(){for(var e=elData(this._objects.get(this._popoverCurrentObjectId)[0].reactButton,"reaction-type-id"),t=elBySelAll(".reactionTypeButton.active",this._getPopover()),i=0,n=t.length;i<n;i++)t[i].classList.remove("active");0!=e&&elBySel('.reactionTypeButton[data-reaction-type-id="'+e+'"]',this._getPopover()).classList.add("active")},_toggleReactPopover:function(e,t,i){if(null!==i&&(i.preventDefault(),i.stopPropagation()),1===Object.keys(REACTION_TYPES).length){var n=REACTION_TYPES[Object.keys(REACTION_TYPES)[0]];this._popoverCurrentObjectId=e,this._react(n.reactionTypeID)}else 0===this._popoverCurrentObjectId||this._popoverCurrentObjectId!==e?this._openReactPopover(e,t):this._closePopover(e,t)},_openReactPopover:function(e,t){if(0!==this._popoverCurrentObjectId&&this._closePopover(),this._popoverCurrentObjectId=e,this._markReactionAsActive(),h.set(this._getPopover(),t,{pointer:!0,horizontal:this._options.isButtonGroupNavigation?"left":"center",vertical:"top"}),this._options.isButtonGroupNavigation){t.closest("nav").style.opacity="1"}this._getPopover().classList.remove("forceHide"),this._getPopover().classList.add("active")},_getPopover:function(){if(null==this._popover){this._popover=elCreate("div"),this._popover.className="reactionPopover forceHide";var e=elCreate("div");e.className="reactionPopoverContent";var t=elCreate("ul"),i=this._getSortedReactionTypes();for(var n in i)if(i.hasOwnProperty(n)){var a=i[n],r=elCreate("li");r.className="reactionTypeButton jsTooltip",elData(r,"reaction-type-id",a.reactionTypeID),elData(r,"title",a.title),r.title=a.title;var s=elCreate("span");s.classList="reactionTypeButtonTitle",s.innerHTML=a.title,r.innerHTML=a.renderedIcon,r.appendChild(s),r.addEventListener(WCF_CLICK_EVENT,this._react.bind(this,a.reactionTypeID)),t.appendChild(r)}e.appendChild(t),this._popover.appendChild(e);var l=elCreate("span");l.className="elementPointer",l.appendChild(elCreate("span")),this._popover.appendChild(l),document.body.appendChild(this._popover),o.trigger()}return this._popover},_getSortedReactionTypes:function(){var e=[];for(var t in REACTION_TYPES)REACTION_TYPES.hasOwnProperty(t)&&e.push(REACTION_TYPES[t]);return e.sort(function(e,t){return e.showOrder-t.showOrder}),e},_closePopover:function(){0!==this._popoverCurrentObjectId&&(this._getPopover().classList.remove("active"),this._options.isButtonGroupNavigation&&this._objects.get(this._popoverCurrentObjectId).forEach(function(e){e.reactButton.closest("nav").style.cssText=""}),this._popoverCurrentObjectId=0)},_react:function(t){this._options.parameters.reactionTypeID=t,this._options.parameters.data.containerID=this._currentReactionTypeId,this._options.parameters.data.objectID=this._popoverCurrentObjectId,this._options.parameters.data.objectType=this._objectType,e.api(this,{parameters:this._options.parameters}),this._closePopover()},_ajaxSuccess:function(e){this.countButtons.updateCountButtons(e.returnValues.objectID,e.returnValues.reactions),this._updateReactButton(e.returnValues.objectID,e.returnValues.reactionTypeID)},_ajaxSetup:function(){return{data:{actionName:"react",className:"\\wcf\\data\\reaction\\ReactionAction"}}}},m}),define("WoltLabSuite/Core/Ui/Like/Handler",["Ajax","Core","Dictionary","Language","ObjectMap","StringUtil","Dom/ChangeListener","Dom/Util","Ui/Dialog","WoltLabSuite/Core/Ui/User/List","User","WoltLabSuite/Core/Ui/Reaction/Handler"],function(e,t,i,n,a,r,o,s,l,c,d,u){"use strict";function h(e,t){this.init(e,t)}return h.prototype={init:function(e,i){if(""===i.containerSelector)throw new Error("[WoltLabSuite/Core/Ui/Like/Handler] Expected a non-empty string for option 'containerSelector'.");this._containers=new a,this._details=new a,this._objectType=e,this._options=t.extend({badgeClassNames:"",isSingleItem:!1,markListItemAsActive:!1,renderAsButton:!0,summaryPrepend:!0,summaryUseIcon:!0,canDislike:!1,canLike:!1,canLikeOwnContent:!1,canViewSummary:!1,badgeContainerSelector:".messageHeader .messageStatus",buttonAppendToSelector:".messageFooter .messageFooterButtons",buttonBeforeSelector:"",containerSelector:"",summarySelector:".messageFooterGroup"},i),this.initContainers(i,e),o.add("WoltLabSuite/Core/Ui/Like/Handler-"+e,this.initContainers.bind(this)),new u(this._objectType,{containerSelector:this._options.containerSelector,summaryListSelector:".reactionSummaryList"})},initContainers:function(){for(var e,t,i=elBySelAll(this._options.containerSelector),n=!1,a=0,r=i.length;a<r;a++)e=i[a],this._containers.has(e)||(t={badge:null,dislikeButton:null,likeButton:null,summary:null,dislikes:~~elData(e,"like-dislikes"),liked:~~elData(e,"like-liked"),likes:~~elData(e,"like-likes"),objectId:~~elData(e,"object-id"),users:JSON.parse(elData(e,"like-users"))},this._containers.set(e,t),this._buildWidget(e,t),n=!0);n&&o.trigger()},_buildWidget:function(e,t){var i,n,a,o=!0;if(a=this._options.isSingleItem?elBySel(this._options.summarySelector):elBySel(this._options.summarySelector,e),null===a&&(a=this._options.isSingleItem?elBySel(this._options.badgeContainerSelector):elBySel(this._options.badgeContainerSelector,e),o=!1),null!==a){i=elCreate("ul"),i.classList.add("reactionSummaryList"),o?i.classList.add("likesSummary"):i.classList.add("reactionSummaryListTiny");for(var l in t.users)if("reactionTypeID"!==l&&REACTION_TYPES.hasOwnProperty(l)){var c=elCreate("li");c.className="reactCountButton",elData(c,"reaction-type-id",l);var u=elCreate("span");u.className="reactionCount",u.innerHTML=r.shortUnit(t.users[l]),c.appendChild(u),c.innerHTML=c.innerHTML+REACTION_TYPES[l].renderedIcon,i.appendChild(c)}o?this._options.summaryPrepend?s.prepend(i,a):a.appendChild(i):"OL"===a.nodeName||"UL"===a.nodeName?(n=elCreate("li"),n.appendChild(i),a.appendChild(n)):a.appendChild(i),t.badge=i}if(this._options.canLike&&(d.userId!=elData(e,"user-id")||this._options.canLikeOwnContent)){var h=this._options.buttonAppendToSelector?this._options.isSingleItem?elBySel(this._options.buttonAppendToSelector):elBySel(this._options.buttonAppendToSelector,e):null,p=this._options.buttonBeforeSelector?this._options.isSingleItem?elBySel(this._options.buttonBeforeSelector):elBySel(this._options.buttonBeforeSelector,e):null;if(null===p&&null===h)throw new Error("Unable to find insert location for like/dislike buttons.");t.likeButton=this._createButton(e,t.users.reactionTypeID,p,h)}},_createButton:function(e,t,i,a){var r=n.get("wcf.reactions.react"),o=elCreate("li");if(o.className="wcfReactButton",i)var s=i.parentElement.contains("jsMobileNavigation");else var s=a.classList.contains("jsMobileNavigation");var l=elCreate("a");l.className="jsTooltip reactButton",this._options.renderAsButton&&(l.classList.add("button"),s&&l.classList.add("ignoreMobileNavigation")),l.href="#",l.title=r;var c=elCreate("span")
+;c.className="icon icon16 fa-smile-o",void 0===t||0==t?elData(c,"reaction-type-id",0):(elData(l,"reaction-type-id",t),l.classList.add("active")),l.appendChild(c);var d=elCreate("span");return d.className="invisible",d.innerHTML=r,l.appendChild(document.createTextNode(" ")),l.appendChild(d),o.appendChild(l),i?i.parentNode.insertBefore(o,i):a.appendChild(o),l}},h}),define("WoltLabSuite/Core/Ui/Message/InlineEditor",["Ajax","Core","Dictionary","Environment","EventHandler","Language","ObjectMap","Dom/ChangeListener","Dom/Traverse","Dom/Util","Ui/Notification","Ui/ReusableDropdown","WoltLabSuite/Core/Ui/Scroll"],function(e,t,i,n,a,r,o,s,l,c,d,u,h){"use strict";function p(e){this.init(e)}return p.prototype={init:function(e){this._activeDropdownElement=null,this._activeElement=null,this._dropdownMenu=null,this._elements=new o,this._options=t.extend({canEditInline:!1,className:"",containerId:0,dropdownIdentifier:"",editorPrefix:"messageEditor",messageSelector:".jsMessage",quoteManager:null},e),this.rebuild(),s.add("Ui/Message/InlineEdit_"+this._options.className,this.rebuild.bind(this))},rebuild:function(){for(var e,t,i,n=elBySelAll(this._options.messageSelector),a=0,r=n.length;a<r;a++)if(i=n[a],!this._elements.has(i)){e=elBySel(".jsMessageEditButton",i),null!==e&&(t=elDataBool(i,"can-edit"),this._options.canEditInline||elDataBool(i,"can-edit-inline")?(e.addEventListener(WCF_CLICK_EVENT,this._clickDropdown.bind(this,i)),e.classList.add("jsDropdownEnabled"),t&&e.addEventListener("dblclick",this._click.bind(this,i))):t&&e.addEventListener(WCF_CLICK_EVENT,this._click.bind(this,i)));var o=elBySel(".messageBody",i),s=elBySel(".messageFooter",i),l=elBySel(".messageHeader",i);this._elements.set(i,{button:e,messageBody:o,messageBodyEditor:null,messageFooter:s,messageFooterButtons:elBySel(".messageFooterButtons",s),messageHeader:l,messageText:elBySel(".messageText",o)})}},_click:function(t,i){null===t&&(t=this._activeDropdownElement),i&&i.preventDefault(),null===this._activeElement?(this._activeElement=t,this._prepare(),e.api(this,{actionName:"beginEdit",parameters:{containerID:this._options.containerId,objectID:this._getObjectId(t)}})):d.show("wcf.message.error.editorAlreadyInUse",null,"warning")},_clickDropdown:function(e,i){i.preventDefault();var n=i.currentTarget;if(!n.classList.contains("dropdownToggle")){if(n.classList.add("dropdownToggle"),n.parentNode.classList.add("dropdown"),function(e,t){e.addEventListener(WCF_CLICK_EVENT,function(i){i.preventDefault(),i.stopPropagation(),this._activeDropdownElement=t,u.toggleDropdown(this._options.dropdownIdentifier,e)}.bind(this))}.bind(this)(n,e),null===this._dropdownMenu){this._dropdownMenu=elCreate("ul"),this._dropdownMenu.className="dropdownMenu";var r=this._dropdownGetItems();a.fire("com.woltlab.wcf.inlineEditor","dropdownInit_"+this._options.dropdownIdentifier,{items:r}),this._dropdownBuild(r),u.init(this._options.dropdownIdentifier,this._dropdownMenu),u.registerCallback(this._options.dropdownIdentifier,this._dropdownToggle.bind(this))}setTimeout(function(){t.triggerEvent(n,WCF_CLICK_EVENT)},10)}},_dropdownBuild:function(e){for(var t,i,n,a=this._clickDropdownItem.bind(this),o=0,s=e.length;o<s;o++)t=e[o],n=elCreate("li"),elData(n,"item",t.item),"divider"===t.item?n.className="dropdownDivider":(i=elCreate("span"),i.textContent=r.get(t.label),n.appendChild(i),"editItem"===t.item?n.addEventListener(WCF_CLICK_EVENT,this._click.bind(this,null)):n.addEventListener(WCF_CLICK_EVENT,a)),this._dropdownMenu.appendChild(n)},_dropdownToggle:function(e,t){var i=this._elements.get(this._activeDropdownElement);if(i.button.parentNode.classList["open"===t?"add":"remove"]("dropdownOpen"),i.messageFooterButtons.classList["open"===t?"add":"remove"]("forceVisible"),"open"===t){var n=this._dropdownOpen();a.fire("com.woltlab.wcf.inlineEditor","dropdownOpen_"+this._options.dropdownIdentifier,{element:this._activeDropdownElement,visibility:n});for(var r,o,s=!1,l=0;l<this._dropdownMenu.childElementCount;l++)o=this._dropdownMenu.children[l],r=elData(o,"item"),"divider"===r?s?(elShow(o),s=!1):elHide(o):objOwns(n,r)&&!1===n[r]?(elHide(o),l>0&&l+1===this._dropdownMenu.childElementCount&&"divider"===elData(o.previousElementSibling,"item")&&elHide(o.previousElementSibling)):(elShow(o),s=!0)}},_dropdownGetItems:function(){},_dropdownOpen:function(){},_dropdownSelect:function(e){},_clickDropdownItem:function(e){e.preventDefault();var t=elData(e.currentTarget,"item"),i={cancel:!1,element:this._activeDropdownElement,item:t};a.fire("com.woltlab.wcf.inlineEditor","dropdownItemClick_"+this._options.dropdownIdentifier,i),!0===i.cancel?e.preventDefault():this._dropdownSelect(t)},_prepare:function(){var e=this._elements.get(this._activeElement),t=elCreate("div");t.className="messageBody editor",e.messageBodyEditor=t;var i=elCreate("span");i.className="icon icon48 fa-spinner",t.appendChild(i),c.insertAfter(t,e.messageBody),elHide(e.messageBody)},_showEditor:function(e){var t=this._getEditorId(),i=this._elements.get(this._activeElement);this._activeElement.classList.add("jsInvalidQuoteTarget");var r=l.childByClass(i.messageBodyEditor,"icon");elRemove(r);var o=i.messageBodyEditor,s=elCreate("div");s.className="editorContainer",c.setInnerHtml(s,e.returnValues.template),o.appendChild(s);var d=elBySel(".formSubmit",s);elBySel('button[data-type="save"]',d).addEventListener(WCF_CLICK_EVENT,this._save.bind(this)),elBySel('button[data-type="cancel"]',d).addEventListener(WCF_CLICK_EVENT,this._restoreMessage.bind(this)),a.add("com.woltlab.wcf.redactor","submitEditor_"+t,function(e){e.cancel=!0,this._save()}.bind(this)),elHide(i.messageHeader),elHide(i.messageFooter);var u=elById(t);"redactor"===n.editor()?window.setTimeout(function(){this._options.quoteManager&&this._options.quoteManager.setAlternativeEditor(t),h.element(this._activeElement)}.bind(this),250):u.focus()},_restoreMessage:function(){var e=this._elements.get(this._activeElement);this._destroyEditor(),elRemove(e.messageBodyEditor),e.messageBodyEditor=null,elShow(e.messageBody),elShow(e.messageFooter),elShow(e.messageHeader),this._activeElement.classList.remove("jsInvalidQuoteTarget"),this._activeElement=null,this._options.quoteManager&&this._options.quoteManager.clearAlternativeEditor()},_save:function(){var t={containerID:this._options.containerId,data:{message:""},objectID:this._getObjectId(this._activeElement),removeQuoteIDs:this._options.quoteManager?this._options.quoteManager.getQuotesMarkedForRemoval():[]},i=this._getEditorId(),n=elById("settings_"+i);n&&elBySelAll("input, select, textarea",n,function(e){if("INPUT"!==e.nodeName||"checkbox"!==e.type&&"radio"!==e.type||e.checked){var i=e.name;if(t.hasOwnProperty(i))throw new Error("Variable overshadowing, key '"+i+"' is already present.");t[i]=e.value.trim()}}),a.fire("com.woltlab.wcf.redactor2","getText_"+i,t.data);var r=this._validate(t);r instanceof Promise||(r=!1===r?Promise.reject():Promise.resolve()),r.then(function(){a.fire("com.woltlab.wcf.redactor2","submit_"+i,t),e.api(this,{actionName:"save",parameters:t}),this._hideEditor()}.bind(this),function(e){console.log("Validation of post edit failed: "+e)})},_validate:function(e){elBySelAll(".innerError",this._activeElement,elRemove);var t={api:this,parameters:e,valid:!0,promises:[]};return a.fire("com.woltlab.wcf.redactor2","validate_"+this._getEditorId(),t),t.promises.push(Promise[t.valid?"resolve":"reject"]()),Promise.all(t.promises)},throwError:function(e,t){elInnerError(e,t)},_showMessage:function(e){var t=this._activeElement,i=this._getEditorId(),n=this._elements.get(t),r=elBySelAll(".attachmentThumbnailList, .attachmentFileList",n.messageFooter);if(c.setInnerHtml(l.childByClass(n.messageBody,"messageText"),e.returnValues.message),"string"==typeof e.returnValues.attachmentList){for(var o=0,s=r.length;o<s;o++)elRemove(r[o]);var u=elCreate("div");c.setInnerHtml(u,e.returnValues.attachmentList);for(var h;u.childNodes.length;)h=u.childNodes[u.childNodes.length-1],n.messageFooter.insertBefore(h,n.messageFooter.firstChild)}if("string"==typeof e.returnValues.poll){var p=elBySel(".pollContainer",n.messageBody);null!==p&&elRemove(p.parentNode);var f=elCreate("div");f.className="jsInlineEditorHideContent",c.setInnerHtml(f,e.returnValues.poll),c.prepend(f,n.messageBody)}this._restoreMessage(),this._updateHistory(this._getHash(this._getObjectId(t))),a.fire("com.woltlab.wcf.redactor","autosaveDestroy_"+i),d.show(),this._options.quoteManager&&(this._options.quoteManager.clearAlternativeEditor(),this._options.quoteManager.countQuotes())},_hideEditor:function(){var e=this._elements.get(this._activeElement);elHide(l.childByClass(e.messageBodyEditor,"editorContainer"));var t=elCreate("span");t.className="icon icon48 fa-spinner",e.messageBodyEditor.appendChild(t)},_restoreEditor:function(){var e=this._elements.get(this._activeElement),t=elBySel(".fa-spinner",e.messageBodyEditor);elRemove(t);var i=l.childByClass(e.messageBodyEditor,"editorContainer");null!==i&&elShow(i)},_destroyEditor:function(){a.fire("com.woltlab.wcf.redactor2","autosaveDestroy_"+this._getEditorId()),a.fire("com.woltlab.wcf.redactor2","destroy_"+this._getEditorId())},_getHash:function(e){return"#message"+e},_updateHistory:function(e){window.location.hash=e},_getEditorId:function(){return this._options.editorPrefix+this._getObjectId(this._activeElement)},_getObjectId:function(e){return~~elData(e,"object-id")},_ajaxFailure:function(e){var t=this._elements.get(this._activeElement),i=elBySel(".redactor-layer",t.messageBodyEditor);return null===i?(this._restoreMessage(),!0):(this._restoreEditor(),!e||void 0===e.returnValues||void 0===e.returnValues.realErrorMessage||(elInnerError(i,e.returnValues.realErrorMessage),!1))},_ajaxSuccess:function(e){switch(e.actionName){case"beginEdit":this._showEditor(e);break;case"save":this._showMessage(e)}},_ajaxSetup:function(){return{data:{className:this._options.className,interfaceName:"wcf\\data\\IMessageInlineEditorAction"},silent:!0}},legacyEdit:function(e){this._click(elById(e),null)}},p}),define("WoltLabSuite/Core/Ui/Message/Manager",["Ajax","Core","Dictionary","Language","Dom/ChangeListener","Dom/Util"],function(e,t,i,n,a,r){"use strict";function o(e){this.init(e)}return o.prototype={init:function(e){this._elements=null,this._options=t.extend({className:"",selector:""},e),this.rebuild(),a.add("Ui/Message/Manager"+this._options.className,this.rebuild.bind(this))},rebuild:function(){this._elements=new i;for(var e,t=elBySelAll(this._options.selector),n=0,a=t.length;n<a;n++)e=t[n],this._elements.set(elData(e,"object-id"),e)},getPermission:function(e,t){t="can-"+this._getAttributeName(t);var i=this._elements.get(e);if(void 0===i)throw new Error("Unknown object id '"+e+"' for selector '"+this._options.selector+"'");return elDataBool(i,t)},getPropertyValue:function(e,t,i){var n=this._elements.get(e);if(void 0===n)throw new Error("Unknown object id '"+e+"' for selector '"+this._options.selector+"'");return window[i?"elDataBool":"elData"](n,this._getAttributeName(t))},update:function(t,i,n){e.api(this,{actionName:i,parameters:n||{},objectIDs:[t]})},updateItems:function(e,t){Array.isArray(e)||(e=[e]);for(var i,n=0,a=e.length;n<a;n++)if(void 0!==(i=this._elements.get(e[n])))for(var r in t)t.hasOwnProperty(r)&&this._update(i,r,t[r])},updateAllItems:function(e){var t=[];this._elements.forEach(function(e,i){t.push(i)}.bind(this)),this.updateItems(t,e)},setNote:function(e,t,i){var n=this._elements.get(e);if(void 0===n)throw new Error("Unknown object id '"+e+"' for selector '"+this._options.selector+"'");var a=elBySel(".messageFooterNotes",n),r=elBySel("."+t,a);i?(null===r&&(r=elCreate("p"),r.className="messageFooterNote "+t,a.appendChild(r)),r.innerHTML=i):null!==r&&elRemove(r)},_update:function(e,t,i){elData(e,this._getAttributeName(t),i);var n=1==i||!0===i||"true"===i;this._updateState(e,t,i,n)},_updateState:function(e,t,i,n){switch(t){case"isDeleted":e.classList[n?"add":"remove"]("messageDeleted"),this._toggleMessageStatus(e,"jsIconDeleted","wcf.message.status.deleted","red",n);break;case"isDisabled":e.classList[n?"add":"remove"]("messageDisabled"),this._toggleMessageStatus(e,"jsIconDisabled","wcf.message.status.disabled","green",n)}},_toggleMessageStatus:function(e,t,i,a,o){var s=elBySel(".messageStatus",e);if(null===s){var l=elBySel(".messageHeaderMetaData",e);if(null===l)return;s=elCreate("ul"),s.className="messageStatus",r.insertAfter(s,l)}var c=elBySel("."+t,s);if(o){if(null!==c)return;c=elCreate("span"),c.className="badge label "+a+" "+t,c.textContent=n.get(i);var d=elCreate("li");d.appendChild(c),s.appendChild(d)}else{if(null===c)return;elRemove(c.parentNode)}},_getAttributeName:function(e){if(-1!==e.indexOf("-"))return e;for(var t,i="",n=e.split(/([A-Z][a-z]+)/),a=0,r=n.length;a<r;a++)t=n[a],t.length&&(i.length&&(i+="-"),i+=t.toLowerCase());return i},_ajaxSuccess:function(){throw new Error("Method _ajaxSuccess() must be implemented by deriving functions.")},_ajaxSetup:function(){return{data:{className:this._options.className}}}},o}),define("WoltLabSuite/Core/Ui/Message/Reply",["Ajax","Core","EventHandler","Language","Dom/ChangeListener","Dom/Util","Dom/Traverse","Ui/Dialog","Ui/Notification","WoltLabSuite/Core/Ui/Scroll","EventKey","User","WoltLabSuite/Core/Controller/Captcha"],function(e,t,i,n,a,r,o,s,l,c,d,u,h){"use strict";function p(e){this.init(e)}return p.prototype={init:function(e){this._options=t.extend({ajax:{className:""},quoteManager:null,successMessage:"wcf.global.success.add"},e),this._container=elById("messageQuickReply"),this._content=elBySel(".messageContent",this._container),this._textarea=elById("text"),this._editor=null,this._guestDialogId="",this._loadingOverlay=null,elBySel(".message",this._container).classList.add("jsInvalidQuoteTarget");var i=this._submit.bind(this);elBySel('button[data-type="save"]',this._container).addEventListener(WCF_CLICK_EVENT,i);for(var n=elBySelAll(".jsQuickReply"),a=0,r=n.length;a<r;a++)n[a].addEventListener(WCF_CLICK_EVENT,function(e){e.preventDefault(),this._getEditor().WoltLabReply.showEditor(),c.element(this._container,function(){this._getEditor().WoltLabCaret.endOfEditor()}.bind(this))}.bind(this))},_submitGuestDialog:function(e){if("keypress"!==e.type||d.Enter(e)){var i=elBySel("input[name=username]",e.currentTarget.closest(".dialogContent"));if(""===i.value)return elInnerError(i,n.get("wcf.global.form.error.empty")),void i.closest("dl").classList.add("formError");var a={parameters:{data:{username:i.value}}},r=elData(e.currentTarget,"captcha-id");if(h.has(r)){var o=h.getData(r);o instanceof Promise?o.then(function(e){a=t.extend(a,e),this._submit(void 0,a)}.bind(this)):(a=t.extend(a,h.getData(r)),this._submit(void 0,a))}else this._submit(void 0,a)}},_submit:function(n,a){if(n&&n.preventDefault(),(!this._content.classList.contains("loading")||this._guestDialogId&&s.isOpen(this._guestDialogId))&&this._validate()){this._showLoadingOverlay();var o=r.getDataAttributes(this._container,"data-",!0,!0);o.data={message:this._getEditor().code.get()},o.removeQuoteIDs=this._options.quoteManager?this._options.quoteManager.getQuotesMarkedForRemoval():[];var l=elById("settings_text");l&&elBySelAll("input, select, textarea",l,function(e){if("INPUT"!==e.nodeName||"checkbox"!==e.type&&"radio"!==e.type||e.checked){var t=e.name;if(o.hasOwnProperty(t))throw new Error("Variable overshadowing, key '"+t+"' is already present.");o[t]=e.value.trim()}}),i.fire("com.woltlab.wcf.redactor2","submit_text",o.data),u.userId||a||(o.requireGuestDialog=!0),e.api(this,t.extend({parameters:o},a))}},_validate:function(){if(elBySelAll(".innerError",this._container,elRemove),this._getEditor().utils.isEmpty())return this.throwError(this._textarea,n.get("wcf.global.form.error.empty")),!1;var e={api:this,editor:this._getEditor(),message:this._getEditor().code.get(),valid:!0};return i.fire("com.woltlab.wcf.redactor2","validate_text",e),!1!==e.valid},throwError:function(e,t){elInnerError(e,"empty"===t?n.get("wcf.global.form.error.empty"):t)},_showLoadingOverlay:function(){null===this._loadingOverlay&&(this._loadingOverlay=elCreate("div"),this._loadingOverlay.className="messageContentLoadingOverlay",this._loadingOverlay.innerHTML='<span class="icon icon96 fa-spinner"></span>'),this._content.classList.add("loading"),this._content.appendChild(this._loadingOverlay)},_hideLoadingOverlay:function(){this._content.classList.remove("loading");var e=elBySel(".messageContentLoadingOverlay",this._content);null!==e&&e.parentNode.removeChild(e)},_reset:function(){this._getEditor().code.set("<p>​</p>"),i.fire("com.woltlab.wcf.redactor2","reset_text")},_handleError:function(e){var t={api:this,cancel:!1,returnValues:e.returnValues};i.fire("com.woltlab.wcf.redactor2","handleError_text",t),!0!==t.cancel&&this.throwError(this._textarea,e.returnValues.realErrorMessage)},_getEditor:function(){if(null===this._editor){if("function"!=typeof window.jQuery)throw new Error("Unable to access editor, jQuery has not been loaded yet.");this._editor=window.jQuery(this._textarea).data("redactor")}return this._editor},_insertMessage:function(e){if(this._getEditor().WoltLabAutosave.reset(),e.returnValues.url)window.location==e.returnValues.url&&window.location.reload(),window.location=e.returnValues.url;else{if(e.returnValues.template){var t;if("DESC"===elData(this._container,"sort-order"))r.insertHtml(e.returnValues.template,this._container,"after"),t=r.identify(this._container.nextElementSibling);else{var i=this._container;i.previousElementSibling&&i.previousElementSibling.classList.contains("messageListPagination")&&(i=i.previousElementSibling),r.insertHtml(e.returnValues.template,i,"before"),t=r.identify(i.previousElementSibling)}elData(this._container,"last-post-time",e.returnValues.lastPostTime),window.history.replaceState(void 0,"","#"+t),c.element(elById(t))}l.show(n.get(this._options.successMessage)),this._options.quoteManager&&this._options.quoteManager.countQuotes(),a.trigger()}},_ajaxSuccess:function(e){if(!u.userId&&!e.returnValues.guestDialogID)throw new Error("Missing 'guestDialogID' return value for guest.");if(!u.userId&&e.returnValues.guestDialog){s.openStatic(e.returnValues.guestDialogID,e.returnValues.guestDialog,{closable:!1,onClose:function(){h.has(e.returnValues.guestDialogID)&&h.delete(e.returnValues.guestDialogID)},title:n.get("wcf.global.confirmation.title")});var t=s.getDialog(e.returnValues.guestDialogID);elBySel("input[type=submit]",t.content).addEventListener(WCF_CLICK_EVENT,this._submitGuestDialog.bind(this)),elBySel("input[type=text]",t.content).addEventListener("keypress",this._submitGuestDialog.bind(this)),this._guestDialogId=e.returnValues.guestDialogID}else this._insertMessage(e),u.userId||s.close(e.returnValues.guestDialogID),this._reset(),this._hideLoadingOverlay()},_ajaxFailure:function(e){return this._hideLoadingOverlay(),null===e||void 0===e.returnValues||void 0===e.returnValues.realErrorMessage||(this._handleError(e),!1)},_ajaxSetup:function(){return{data:{actionName:"quickReply",className:this._options.ajax.className,interfaceName:"wcf\\data\\IMessageQuickReplyAction"},silent:!0}}},p}),define("WoltLabSuite/Core/Ui/Message/Share",["EventHandler","StringUtil"],function(e,t){"use strict";return{_pageDescription:"",_pageUrl:"",init:function(){var i=elBySel('meta[property="og:title"]');null!==i&&(this._pageDescription=encodeURIComponent(i.content));var n=elBySel('meta[property="og:url"]');null!==n&&(this._pageUrl=encodeURIComponent(n.content)),elBySelAll(".jsMessageShareButtons",null,function(i){i.classList.remove("jsMessageShareButtons");var n=encodeURIComponent(t.unescapeHTML(elData(i,"url")||""));n||(n=this._pageUrl);var a={facebook:{link:elBySel(".jsShareFacebook",i),share:function(e){this._share("facebook","https://www.facebook.com/sharer.php?u={pageURL}&t={text}",!0,n)}.bind(this)},google:{link:elBySel(".jsShareGoogle",i),share:function(e){this._share("google","https://plus.google.com/share?url={pageURL}",!1,n)}.bind(this)},reddit:{link:elBySel(".jsShareReddit",i),share:function(e){this._share("reddit","https://ssl.reddit.com/submit?url={pageURL}",!1,n)}.bind(this)},twitter:{link:elBySel(".jsShareTwitter",i),share:function(e){this._share("twitter","https://twitter.com/share?url={pageURL}&text={text}",!1,n)}.bind(this)},linkedIn:{link:elBySel(".jsShareLinkedIn",i),share:function(e){this._share("linkedIn","https://www.linkedin.com/cws/share?url={pageURL}",!1,n)}.bind(this)},pinterest:{link:elBySel(".jsSharePinterest",i),share:function(e){this._share("pinterest","https://www.pinterest.com/pin/create/link/?url={pageURL}&description={text}",!1,n)}.bind(this)},xing:{link:elBySel(".jsShareXing",i),share:function(e){this._share("xing","https://www.xing.com/social_plugins/share?url={pageURL}",!1,n)}.bind(this)},whatsApp:{link:elBySel(".jsShareWhatsApp",i),share:function(){window.location.href="whatsapp://send?text="+this._pageDescription+"%20"+n}.bind(this)}};e.fire("com.woltlab.wcf.message.share","shareProvider",{container:i,providers:a,pageDescription:this._pageDescription,pageUrl:this._pageUrl});for(var r in a)a.hasOwnProperty(r)&&null!==a[r].link&&a[r].link.addEventListener(WCF_CLICK_EVENT,a[r].share)}.bind(this))},_share:function(e,t,i,n){n||(n=this._pageUrl),window.open(t.replace(/\{pageURL}/,n).replace(/\{text}/,this._pageDescription+(i?"%20"+n:"")),e,"height=600,width=600")}}}),define("WoltLabSuite/Core/Ui/Page/Search",["Ajax","EventKey","Language","StringUtil","Dom/Util","Ui/Dialog"],function(e,t,i,n,a,r){"use strict";var o,s,l,c=null;return{open:function(e){o=e,r.open(this)},_search:function(t){t.preventDefault();var n=c.parentNode,a=c.value.trim();if(a.length<3)return void elInnerError(n,i.get("wcf.page.search.error.tooShort"));elInnerError(n,!1),e.api(this,{parameters:{searchString:a}})},_click:function(e){e.preventDefault();var t=e.currentTarget,i=elBySel("h3",t).textContent.replace(/['"]/g,"");o(elData(t,"page-id")+"#"+i),r.close(this)},_ajaxSuccess:function(e){for(var t,a="",r=0,o=e.returnValues.length;r<o;r++)t=e.returnValues[r],a+='<li><div class="containerHeadline pointer" data-page-id="'+t.pageID+'"><h3>'+n.escapeHTML(t.name)+"</h3><small>"+n.escapeHTML(t.displayLink)+"</small></div></li>";l.innerHTML=a,window[a?"elShow":"elHide"](s),a?elBySelAll(".containerHeadline",l,function(e){e.addEventListener(WCF_CLICK_EVENT,this._click.bind(this))}.bind(this)):elInnerError(c.parentNode,i.get("wcf.page.search.error.noResults"))},_ajaxSetup:function(){return{data:{actionName:"search",className:"wcf\\data\\page\\PageAction"}}},_dialogSetup:function(){return{id:"wcfUiPageSearch",options:{onSetup:function(){var e=this._search.bind(this);c=elById("wcfUiPageSearchInput"),c.addEventListener("keydown",function(i){t.Enter(i)&&e(i)}),c.nextElementSibling.addEventListener(WCF_CLICK_EVENT,e),s=elById("wcfUiPageSearchResultContainer"),l=elById("wcfUiPageSearchResultList")}.bind(this),onShow:function(){c.focus()},title:i.get("wcf.page.search")},source:'<div class="section"><dl><dt><label for="wcfUiPageSearchInput">'+i.get("wcf.page.search.name")+'</label></dt><dd><div class="inputAddon"><input type="text" id="wcfUiPageSearchInput" class="long"><a href="#" class="inputSuffix"><span class="icon icon16 fa-search"></span></a></div></dd></dl></div><section id="wcfUiPageSearchResultContainer" class="section" style="display: none;"><header class="sectionHeader"><h2 class="sectionTitle">'+i.get("wcf.page.search.results")+'</h2></header><ol id="wcfUiPageSearchResultList" class="containerList"></ol></section>'}}}}),define("WoltLabSuite/Core/Ui/Sortable/List",["Core","Ui/Screen"],function(e,t){"use strict";function i(e){this.init(e)}return i.prototype={init:function(i){this._options=e.extend({containerId:"",className:"",offset:0,options:{},isSimpleSorting:!1,additionalParameters:{}},i),t.on("screen-sm-md",{match:this._enable.bind(this,!0),unmatch:this._disable.bind(this),setup:this._enable.bind(this,!0)}),t.on("screen-lg",{match:this._enable.bind(this,!1),unmatch:this._disable.bind(this),setup:this._enable.bind(this,!1)})},_enable:function(e){var t=this._options.options;e&&(t.handle=".sortableNodeHandle"),new window.WCF.Sortable.List(this._options.containerId,this._options.className,this._options.offset,t,this._options.isSimpleSorting,this._options.additionalParameters)},_disable:function(){window.jQuery("#"+this._options.containerId+" .sortableList")[this._options.isSimpleSorting?"sortable":"nestedSortable"]("destroy")}},i}),define("WoltLabSuite/Core/Ui/Poll/Editor",["Core","Dom/Util","EventHandler","EventKey","Language","WoltLabSuite/Core/Date/Picker","WoltLabSuite/Core/Ui/Sortable/List"],function(e,t,i,n,a,r,o){"use strict";function s(e,t,i,n){this.init(e,t,i,n)}return s.prototype={init:function(t,n,a,r){if(this._container=elById(t),null===this._container)throw new Error("Unknown poll editor container with id '"+t+"'.");if(this._wysiwygId=a,""!==a&&null===elById(a))throw new Error("Unknown wysiwyg field with id '"+a+"'.");this.questionField=elById(this._wysiwygId+"Poll_question");var s=elByClass("sortableList",this._container);if(0===s.length)throw new Error("Cannot find poll options list for container with id '"+t+"'.");if(this.optionList=s[0],this.endTimeField=elById(this._wysiwygId+"Poll_endTime"),this.maxVotesField=elById(this._wysiwygId+"Poll_maxVotes"),this.isChangeableYesField=elById(this._wysiwygId+"Poll_isChangeable"),this.isChangeableNoField=elById(this._wysiwygId+"Poll_isChangeable_no"),this.isPublicYesField=elById(this._wysiwygId+"Poll_isPublic"),this.isPublicNoField=elById(this._wysiwygId+"Poll_isPublic_no"),this.resultsRequireVoteYesField=elById(this._wysiwygId+"Poll_resultsRequireVote"),this.resultsRequireVoteNoField=elById(this._wysiwygId+"Poll_resultsRequireVote_no"),this.sortByVotesYesField=elById(this._wysiwygId+"Poll_sortByVotes"),this.sortByVotesNoField=elById(this._wysiwygId+"Poll_sortByVotes_no"),this._optionCount=0,this._options=e.extend({isAjax:!1,maxOptions:20},r),this._createOptionList(n||[]),new o({containerId:t,options:{toleranceElement:"> div"}}),this._options.isAjax)for(var l=["handleError","reset","submit","validate"],c=0,d=l.length;c<d;c++){var u=l[c];i.add("com.woltlab.wcf.redactor2",u+"_"+this._wysiwygId,this["_"+u].bind(this))}else{var h=this._container.closest("form");if(null===h)throw new Error("Cannot find form for container with id '"+t+"'.");h.addEventListener("submit",this._submit.bind(this))}},_addOption:function(e){if(e.preventDefault(),this._optionCount===this._options.maxOptions)return!1;this._createOption(void 0,void 0,e.currentTarget.closest("li"))},_createOption:function(e,i,n){e=e||"",i=~~i||0;var r=elCreate("LI");r.className="sortableNode",elData(r,"option-id",i),n?t.insertAfter(r,n):this.optionList.appendChild(r);var o=elCreate("div");o.className="pollOptionInput",r.appendChild(o);var s=elCreate("span");s.className="icon icon16 fa-arrows sortableNodeHandle",o.appendChild(s);var l=elCreate("a");elAttr(l,"role","button"),elAttr(l,"href","#"),l.className="icon icon16 fa-plus jsTooltip jsAddOption pointer",elAttr(l,"title",a.get("wcf.poll.button.addOption")),l.addEventListener("click",this._addOption.bind(this)),o.appendChild(l);var c=elCreate("a");elAttr(c,"role","button"),elAttr(c,"href","#"),c.className="icon icon16 fa-times jsTooltip jsDeleteOption pointer",elAttr(c,"title",a.get("wcf.poll.button.removeOption")),c.addEventListener("click",this._removeOption.bind(this)),o.appendChild(c);var d=elCreate("input");elAttr(d,"type","text"),d.value=e,elAttr(d,"maxlength",255),d.addEventListener("keydown",this._optionInputKeyDown.bind(this)),d.addEventListener("click",function(){document.activeElement!==this&&this.focus()}),o.appendChild(d),null!==n&&d.focus(),++this._optionCount===this._options.maxOptions&&elBySelAll("span.jsAddOption",this.optionList,function(e){e.classList.remove("pointer"),e.classList.add("disabled")})},_createOptionList:function(e){for(var t=0,i=e.length;t<i;t++){var n=e[t];this._createOption(n.optionValue,n.optionID)}this._optionCount<this._options.maxOptions&&this._createOption()},_handleError:function(e){switch(e.returnValues.fieldName){case this._wysiwygId+"Poll_endTime":case this._wysiwygId+"Poll_maxVotes":var i=e.returnValues.fieldName.replace(this._wysiwygId+"Poll_",""),n=elCreate("small");n.className="innerError",n.innerHTML=a.get("wcf.poll."+i+".error."+e.returnValues.errorType);var r=elById(e.returnValues.fieldName);r.closest("dd");t.prepend(n,r.nextSibling),e.cancel=!0}},_optionInputKeyDown:function(t){n.Enter(t)&&(e.triggerEvent(elByClass("jsAddOption",t.currentTarget.parentNode)[0],"click"),t.preventDefault())},_removeOption:function(e){e.preventDefault(),elRemove(e.currentTarget.closest("li")),this._optionCount--,elBySelAll("span.jsAddOption",this.optionList,function(e){e.classList.add("pointer"),e.classList.remove("disabled")}),0===this.optionList.length&&this._createOption()},_reset:function(){this.questionField.value="",this._optionCount=0,this.optionList.innerHtml="",this._createOption(),r.clear(this.endTimeField),this.maxVotesField.value=1,this.isChangeableYesField.checked=!1,this.isChangeableNoField.checked=!0,this.isPublicYesField=!1,this.isPublicNoField=!0,this.resultsRequireVoteYesField=!1,this.resultsRequireVoteNoField=!0,this.sortByVotesYesField=!1,this.sortByVotesNoField=!0,i.fire("com.woltlab.wcf.poll.editor","reset",{pollEditor:this})},_submit:function(e){for(var t=[],n=0,a=this.optionList.children.length;n<a;n++){var r=this.optionList.children[n],o=elBySel("input[type=text]",r).value.trim();""!==o&&t.push(elData(r,"option-id")+"_"+o)}if(this._options.isAjax)e.poll={},e.poll[this.questionField.id]=this.questionField.value,e.poll[this._wysiwygId+"Poll_options"]=t,e.poll[this.endTimeField.id]=this.endTimeField.value,e.poll[this.maxVotesField.id]=this.maxVotesField.value,e.poll[this.isChangeableYesField.id]=!!this.isChangeableYesField.checked,e.poll[this.isPublicYesField.id]=!!this.isPublicYesField.checked,e.poll[this.resultsRequireVoteYesField.id]=!!this.resultsRequireVoteYesField.checked,e.poll[this.sortByVotesYesField.id]=!!this.sortByVotesYesField.checked,i.fire("com.woltlab.wcf.poll.editor","submit",{event:e,pollEditor:this});else for(var s=this._container.closest("form"),n=0,a=t.length;n<a;n++){var l=elCreate("input");elAttr(l,"type","hidden"),elAttr(l,"name",this._wysiwygId+"Poll_options["+n+"]"),l.value=t[n],s.appendChild(l)}},_validate:function(e){if(""!==this.questionField.value.trim()){for(var t=0,n=0,r=this.optionList.children.length;n<r;n++){""!==elBySel("input[type=text]",this.optionList.children[n]).value.trim()&&t++}if(0===t)e.api.throwError(this._container,a.get("wcf.global.form.error.empty")),e.valid=!1;else{var o=~~this.maxVotesField.value;o&&o>t?(e.api.throwError(this.maxVotesField.parentNode,a.get("wcf.poll.maxVotes.error.invalid")),e.valid=!1):i.fire("com.woltlab.wcf.poll.editor","validate",{data:e,pollEditor:this})}}}},s}),define("WoltLabSuite/Core/Ui/Redactor/Article",["WoltLabSuite/Core/Ui/Article/Search"],function(e){"use strict";function t(e,t){this.init(e,t)}return t.prototype={init:function(e,t){this._editor=e,t.addEventListener(WCF_CLICK_EVENT,this._click.bind(this))},_click:function(t){t.preventDefault(),e.open(this._insert.bind(this))},_insert:function(e){this._editor.buffer.set(),this._editor.insert.text("[wsa='"+e+"'][/wsa]")}},t}),define("WoltLabSuite/Core/Ui/Redactor/Metacode",["EventHandler","Dom/Util"],function(e,t){"use strict";return{convert:function(e){e.textContent=this.convertFromHtml(e.textContent)},convertFromHtml:function(i,n){var a=elCreate("div");a.innerHTML=n;for(var r,o,s,l,c,d,u=elByTag("woltlab-metacode",a);u.length;)s=u[0],l=elData(s,"name"),r=this._parseAttributes(elData(s,"attributes")),o={attributes:r,cancel:!1,metacode:s},e.fire("com.woltlab.wcf.redactor2","metacode_"+l+"_"+i,o),!0!==o.cancel&&(d=this._getOpeningTag(l,r),c=this._getClosingTag(l),s.parentNode===a?(t.prepend(d,this._getFirstParagraph(s)),this._getLastParagraph(s).appendChild(c)):(t.prepend(d,s),s.appendChild(c)),t.unwrapChildNodes(s))
+;for(var h,p=elByTag("kbd",a);p.length;)h=p[0],h.insertBefore(document.createTextNode("[tt]"),h.firstChild),h.appendChild(document.createTextNode("[/tt]")),t.unwrapChildNodes(h);return a.innerHTML},_getOpeningTag:function(e,t){var i="["+e;if(t.length){i+="=";for(var n=0,a=t.length;n<a;n++)n>0&&(i+=","),i+="'"+t[n]+"'"}return document.createTextNode(i+"]")},_getClosingTag:function(e){return document.createTextNode("[/"+e+"]")},_getFirstParagraph:function(e){var t,i;return 0===e.childElementCount?(i=elCreate("p"),e.appendChild(i)):(t=e.children[0],"P"===t.nodeName?i=t:(i=elCreate("p"),e.insertBefore(i,t))),i},_getLastParagraph:function(e){var t,i,n=e.childElementCount;return 0===n?(i=elCreate("p"),e.appendChild(i)):(t=e.children[n-1],"P"===t.nodeName?i=t:(i=elCreate("p"),e.appendChild(i))),i},_parseAttributes:function(e){try{e=JSON.parse(atob(e))}catch(e){}if(!Array.isArray(e))return[];for(var t,i=[],n=0,a=e.length;n<a;n++)t=e[n],"string"==typeof t&&(t=t.replace(/^'(.*)'$/,"$1")),i.push(t);return i}}}),define("WoltLabSuite/Core/Ui/Redactor/Autosave",["Core","Devtools","EventHandler","Language","Dom/Traverse","./Metacode"],function(e,t,i,n,a,r){"use strict";function o(e){this.init(e)}return o.prototype={init:function(t){this._container=null,this._metaData={},this._editor=null,this._element=t,this._isActive=!0,this._isPending=!1,this._key=e.getStoragePrefix()+elData(this._element,"autosave"),this._lastMessage="",this._originalMessage="",this._overlay=null,this._restored=!1,this._timer=null,this._cleanup(),this._element.removeAttribute("data-autosave");var n=a.parentByTag(this._element,"FORM");null!==n&&n.addEventListener("submit",this.destroy.bind(this)),i.add("com.woltlab.wcf.redactor2","getMetaData_"+this._element.id,function(e){for(var t in this._metaData)this._metaData.hasOwnProperty(t)&&(e[t]=this._metaData[t])}.bind(this)),i.add("com.woltlab.wcf.redactor2","reset_"+this._element.id,this.hideOverlay.bind(this)),document.addEventListener("visibilitychange",this._onVisibilityChange.bind(this))},_onVisibilityChange:function(){document.hidden?(this._isActive=!1,this._isPending=!0):(this._isActive=!0,this._isPending=!1)},getInitialValue:function(){if(window.ENABLE_DEVELOPER_TOOLS&&!1===t._internal_.editorAutosave())return this._element.value;var e="";try{e=window.localStorage.getItem(this._key)}catch(e){window.console.warn("Unable to access local storage: "+e.message)}try{e=JSON.parse(e)}catch(t){e=""}if(null!==e&&"object"==typeof e&&e.content){if(1e3*~~elData(this._element,"autosave-last-edit-time")<=e.timestamp){var i=elCreate("div");i.innerHTML=this._element.value;var n=elCreate("div");if(n.innerHTML=e.content,i.innerText.trim()!==n.innerText.trim())return this._originalMessage=this._element.value,this._restored=!0,this._metaData=e.meta||{},e.content}}return this._element.value},getMetaData:function(){return this._metaData},watch:function(e){if(this._editor=e,null!==this._timer)throw new Error("Autosave timer is already active.");this._timer=window.setInterval(this._saveToStorage.bind(this),15e3),this._saveToStorage(),this._isPending=!1},destroy:function(){this.clear(),this._editor=null,window.clearInterval(this._timer),this._timer=null,this._isPending=!1},clear:function(){this._metaData={},this._lastMessage="";try{window.localStorage.removeItem(this._key)}catch(e){window.console.warn("Unable to remove from local storage: "+e.message)}},createOverlay:function(){if(this._restored){var e=elCreate("div");e.className="redactorAutosaveRestored active";var t=elCreate("span");t.textContent=n.get("wcf.editor.autosave.restored"),e.appendChild(t);var i=elCreate("a");i.className="jsTooltip",i.href="#",i.title=n.get("wcf.editor.autosave.keep"),i.innerHTML='<span class="icon icon16 fa-check green"></span>',i.addEventListener(WCF_CLICK_EVENT,function(e){e.preventDefault(),this.hideOverlay()}.bind(this)),e.appendChild(i),i=elCreate("a"),i.className="jsTooltip",i.href="#",i.title=n.get("wcf.editor.autosave.discard"),i.innerHTML='<span class="icon icon16 fa-times red"></span>',i.addEventListener(WCF_CLICK_EVENT,function(e){e.preventDefault(),this.clear();var t=r.convertFromHtml(this._editor.core.element()[0].id,this._originalMessage);this._editor.code.start(t),this._editor.core.textarea().val(this._editor.clean.onSync(this._editor.$editor.html())),this.hideOverlay()}.bind(this)),e.appendChild(i),this._editor.core.box()[0].appendChild(e);var a=function(){this._editor.core.editor()[0].removeEventListener(WCF_CLICK_EVENT,a),this.hideOverlay()}.bind(this);this._editor.core.editor()[0].addEventListener(WCF_CLICK_EVENT,a),this._container=e}},hideOverlay:function(){null!==this._container&&(this._container.classList.remove("active"),window.setTimeout(function(){null!==this._container&&elRemove(this._container),this._container=null,this._originalMessage=""}.bind(this),1e3))},_saveToStorage:function(){if(!this._isActive){if(!this._isPending)return;this._isPending=!1}if(!window.ENABLE_DEVELOPER_TOOLS||!1!==t._internal_.editorAutosave()){var e=this._editor.code.get();if(this._editor.utils.isEmpty(e)&&(e=""),this._lastMessage!==e){if(""===e)return this.clear();try{i.fire("com.woltlab.wcf.redactor2","autosaveMetaData_"+this._element.id,this._metaData),window.localStorage.setItem(this._key,JSON.stringify({content:e,meta:this._metaData,timestamp:Date.now()})),this._lastMessage=e}catch(e){window.console.warn("Unable to write to local storage: "+e.message)}}}},_cleanup:function(){var t,i,n,a,r=Date.now()-6048e5,o=[];for(t=0,n=window.localStorage.length;t<n;t++)if(i=window.localStorage.key(t),0===i.indexOf(e.getStoragePrefix())){try{a=window.localStorage.getItem(i)}catch(e){window.console.warn("Unable to access local storage: "+e.message)}try{a=JSON.parse(a)}catch(e){a={timestamp:0}}(!a||a.timestamp<r)&&o.push(i)}for(t=0,n=o.length;t<n;t++)try{window.localStorage.removeItem(o[t])}catch(e){window.console.warn("Unable to remove from local storage: "+e.message)}}},o}),define("WoltLabSuite/Core/Ui/Redactor/PseudoHeader",[],function(){"use strict";return{getHeight:function(e){var t=~~window.getComputedStyle(e).paddingTop.replace(/px$/,""),i=window.getComputedStyle(e,"::before");t+=~~i.paddingTop.replace(/px$/,""),t+=~~i.paddingBottom.replace(/px$/,"");var n=~~i.height.replace(/px$/,"");return 0===n&&(n=e.scrollHeight,e.classList.add("redactorCalcHeight"),n-=e.scrollHeight,e.classList.remove("redactorCalcHeight")),t+=n}}}),define("WoltLabSuite/Core/Ui/Redactor/Code",["EventHandler","EventKey","Language","StringUtil","Dom/Util","Ui/Dialog","./PseudoHeader","prism/prism-meta"],function(e,t,i,n,a,r,o,s){"use strict";function l(e){this.init(e)}var c=0;return l.prototype={init:function(t){this._editor=t,this._elementId=this._editor.$element[0].id,this._pre=null,e.add("com.woltlab.wcf.redactor2","bbcode_code_"+this._elementId,this._bbcodeCode.bind(this)),e.add("com.woltlab.wcf.redactor2","observe_load_"+this._elementId,this._observeLoad.bind(this)),this._editor.opts.activeButtonsStates.pre="code",this._callbackEdit=this._edit.bind(this),this._observeLoad()},_bbcodeCode:function(e){e.cancel=!0;var t=this._editor.selection.block();t&&"PRE"===t.nodeName&&t.classList.contains("woltlabHtml")||(this._editor.button.toggle({},"pre","func","block.format"),(t=this._editor.selection.block())&&"PRE"===t.nodeName&&!t.classList.contains("woltlabHtml")&&(1===t.childElementCount&&"BR"===t.children[0].nodeName&&t.removeChild(t.children[0]),this._setTitle(t),t.addEventListener(WCF_CLICK_EVENT,this._callbackEdit),this._editor.caret.end(t)))},_observeLoad:function(){elBySelAll("pre:not(.woltlabHtml)",this._editor.$editor[0],function(e){e.addEventListener("mousedown",this._callbackEdit),this._setTitle(e)}.bind(this))},_edit:function(e){var t=e.currentTarget;0===c&&(c=o.getHeight(t));var i=a.offset(t);e.pageY>i.top&&e.pageY<i.top+c&&(e.preventDefault(),this._editor.selection.save(),this._pre=t,r.open(this))},_dialogSubmit:function(){var e="redactor-code-"+this._elementId;["file","highlighter","line"].forEach(function(t){elData(this._pre,t,elById(e+"-"+t).value)}.bind(this)),this._setTitle(this._pre),this._editor.caret.after(this._pre),r.close(this)},_setTitle:function(e){var t=elData(e,"file"),n=elData(e,"highlighter");n=this._editor.opts.woltlab.highlighters.hasOwnProperty(n)?this._editor.opts.woltlab.highlighters[n]:"";var a=i.get("wcf.editor.code.title",{file:t,highlighter:n});elData(e,"title")!==a&&elData(e,"title",a)},_delete:function(e){e.preventDefault();var t=this._pre.nextElementSibling||this._pre.previousElementSibling;null===t&&this._pre.parentNode!==this._editor.core.editor()[0]&&(t=this._pre.parentNode),null===t?(this._editor.code.set(""),this._editor.focus.end()):(elRemove(this._pre),this._editor.caret.end(t)),r.close(this)},_dialogSetup:function(){var e="redactor-code-"+this._elementId,t=e+"-button-delete",a=e+"-button-save",o=e+"-file",l=e+"-highlighter",c=e+"-line";return{id:e,options:{onClose:function(){this._editor.selection.restore(),r.destroy(this)}.bind(this),onSetup:function(){elById(t).addEventListener(WCF_CLICK_EVENT,this._delete.bind(this));var e='<option value="">'+i.get("wcf.editor.code.highlighter.detect")+"</option>";e+='<option value="plain">'+i.get("wcf.editor.code.highlighter.plain")+"</option>";var a=this._editor.opts.woltlab.highlighters.map(function(e){return[e,s[e].title]});a.sort(function(e,t){return e[1]<t[1]?-1:e[1]>t[1]?1:0}),a.forEach(function(t){e+='<option value="'+t[0]+'">'+n.escapeHTML(t[1])+"</option>"}.bind(this)),elById(l).innerHTML=e}.bind(this),onShow:function(){elById(l).value=elData(this._pre,"highlighter");var e=elData(this._pre,"line");elById(c).value=""===e?1:~~e,elById(o).value=elData(this._pre,"file")}.bind(this),title:i.get("wcf.editor.code.edit")},source:'<div class="section"><dl><dt><label for="'+l+'">'+i.get("wcf.editor.code.highlighter")+'</label></dt><dd><select id="'+l+'"></select><small>'+i.get("wcf.editor.code.highlighter.description")+'</small></dd></dl><dl><dt><label for="'+c+'">'+i.get("wcf.editor.code.line")+'</label></dt><dd><input type="number" id="'+c+'" min="0" value="1" class="long" data-dialog-submit-on-enter="true"><small>'+i.get("wcf.editor.code.line.description")+'</small></dd></dl><dl><dt><label for="'+o+'">'+i.get("wcf.editor.code.file")+'</label></dt><dd><input type="text" id="'+o+'" class="long" data-dialog-submit-on-enter="true"><small>'+i.get("wcf.editor.code.file.description")+'</small></dd></dl></div><div class="formSubmit"><button id="'+a+'" class="buttonPrimary" data-type="submit">'+i.get("wcf.global.button.save")+'</button><button id="'+t+'">'+i.get("wcf.global.button.delete")+"</button></div>"}}},l}),define("WoltLabSuite/Core/Ui/Redactor/Format",["Dom/Util"],function(e){"use strict";var t=function(e){for(var t=window.getSelection().anchorNode;t;){if(t===e)return!0;t=t.parentNode}return!1};return{format:function(i,n,a){var r=window.getSelection();if(r.rangeCount){if(!t(i))return void console.error("Invalid selection, range exists outside of the editor:",r.anchorNode);var o=r.getRangeAt(0),s=null,l=null,c=null;if(o.collapsed)c=elCreate("strike"),c.textContent="​",o.insertNode(c),o=document.createRange(),o.selectNodeContents(c),r.removeAllRanges(),r.addRange(o);else{s=elCreate("mark"),l=elCreate("mark");var d=o.cloneRange();d.collapse(!0),d.insertNode(s),d=o.cloneRange(),d.collapse(!1),d.insertNode(l),o=document.createRange(),o.setStartAfter(s),o.setEndBefore(l),r.removeAllRanges(),r.addRange(o),this.removeFormat(i,n),o=document.createRange(),o.setStartAfter(s),o.setEndBefore(l),r.removeAllRanges(),r.addRange(o)}var u=["strike","strikethrough"];null===c&&(u=this._getSelectionMarker(i,r),document.execCommand(u[1]));for(var h,p,f=elBySelAll(u[0],i),m=[],g=0,v=f.length;g<v;g++)p=f[g],h=elCreate("span"),elAttr(h,"style",n+": "+a),e.replaceElement(p,h),m.push(h);var _=m.length;if(_){var b=m[0],w=m[_-1];if(null===c&&b.parentNode===w.parentNode){var y=b.parentNode;"SPAN"===y.nodeName&&""!==y.style.getPropertyValue(n)&&this._isBoundaryElement(b,y,"previous")&&this._isBoundaryElement(w,y,"next")&&e.unwrapChildNodes(y)}o=document.createRange(),o.setStart(b,0),o.setEnd(w,w.childNodes.length),r.removeAllRanges(),r.addRange(o)}null!==s&&(elRemove(s),elRemove(l))}},removeFormat:function(i,n){var a=window.getSelection();if(a.rangeCount){if(!t(i))return void console.error("Invalid selection, range exists outside of the editor:",a.anchorNode);var r=a.getRangeAt(0),o=null;if(r.collapsed){for(var s=r.startContainer,l=[s];;){var c=s.parentNode;if(c===i||"TD"===c.nodeName)break;s=c,l.push(s)}if(this._isEmpty(s.innerHTML)){var d=document.createElement("woltlab-format-marker");return r.insertNode(d),l.forEach(function(t){"SPAN"===t.nodeName&&t.style.getPropertyValue(n)&&e.unwrapChildNodes(t)}),r=document.createRange(),r.selectNode(d),r.collapse(!0),a.removeAllRanges(),a.addRange(r),void elRemove(d)}o=document.createTextNode("​"),r.insertNode(o)}for(var u=elByTag("strike",i);u.length;)e.unwrapChildNodes(u[0]);var h=this._getSelectionMarker(i,window.getSelection());document.execCommand(h[1]),"strike"!==h[0]&&(u=elByTag(h[0],i));for(var p,f;u.length;)f=u[0],p=this._getLastMatchingParent(f,i,n),null!==p&&this._handleParentNodes(f,p,n),elBySelAll("span",f,function(t){t.style.getPropertyValue(n)&&e.unwrapChildNodes(t)}),e.unwrapChildNodes(f);elBySelAll("span",i,function(e){e.parentNode&&!e.textContent.length&&""!==e.style.getPropertyValue(n)&&(1===e.childElementCount&&"MARK"===e.children[0].nodeName&&e.parentNode.insertBefore(e.children[0],e),0===e.childElementCount&&elRemove(e))}),null!==o&&(window.jQuery(i).redactor("caret.after",r.parentNode),elRemove(o))}},_handleParentNodes:function(t,i,n){var a;if(!e.isAtNodeStart(t,i)){a=document.createRange(),a.setStartBefore(i),a.setEndBefore(t);var r=a.extractContents();i.parentNode.insertBefore(r,i)}e.isAtNodeEnd(t,i)||(a=document.createRange(),a.setStartAfter(t),a.setEndAfter(i),r=a.extractContents(),i.parentNode.insertBefore(r,i.nextSibling)),elBySelAll("span",i,function(t){t.style.getPropertyValue(n)&&e.unwrapChildNodes(t)}),e.unwrapChildNodes(i)},_getLastMatchingParent:function(e,t,i){for(var n=e.parentNode,a=null;n!==t;)"SPAN"===n.nodeName&&""!==n.style.getPropertyValue(i)&&(a=n),n=n.parentNode;return a},_isBoundaryElement:function(e,t,i){for(var n=e;n=n[i+"Sibling"];)if(n.nodeType!==Node.TEXT_NODE||""!==n.textContent.replace(/\u200B/,""))return!1;return!0},_getSelectionMarker:function(e,t){for(var i,n,a,r=["DEL","SUB","SUP"],o=0,s=r.length;o<s;o++){if(a=r[o],n=elClosest(t.anchorNode),!(i=null!==elBySel(a.toLowerCase(),n)))for(;n&&n!==e;){if(n.nodeName===a){i=!0;break}n=n.parentNode}if(!i)break;a=void 0}return"DEL"===a||void 0===a?["strike","strikethrough"]:[a.toLowerCase(),a.toLowerCase()+"script"]},_isEmpty:function(e){return e=e.replace(/[\u200B-\u200D\uFEFF]/g,""),e=e.replace(/&nbsp;/gi,""),e=e.replace(/<\/?br\s?\/?>/g,""),e=e.replace(/\s/g,""),e=e.replace(/^<p>[^\W\w\D\d]*?<\/p>$/i,""),e=e.replace(/<iframe(.*?[^>])>$/i,"iframe"),e=e.replace(/<source(.*?[^>])>$/i,"source"),e=e.replace(/<[^\/>][^>]*><\/[^>]+>/gi,""),e=e.replace(/<[^\/>][^>]*><\/[^>]+>/gi,""),""===e.trim()}}}),define("WoltLabSuite/Core/Ui/Redactor/Html",["EventHandler","EventKey","Language","StringUtil","Dom/Util","Ui/Dialog","./PseudoHeader"],function(e,t,i,n,a,r,o){"use strict";function s(e){this.init(e)}var l=0;return s.prototype={init:function(t){this._editor=t,this._elementId=this._editor.$element[0].id,this._pre=null,e.add("com.woltlab.wcf.redactor2","bbcode_woltlabHtml_"+this._elementId,this._bbcodeCode.bind(this)),e.add("com.woltlab.wcf.redactor2","observe_load_"+this._elementId,this._observeLoad.bind(this)),this._editor.opts.activeButtonsStates["woltlab-html"]="woltlabHtml",this._callbackEdit=this._edit.bind(this),this._observeLoad()},_bbcodeCode:function(e){e.cancel=!0;var t=this._editor.selection.block();t&&"PRE"===t.nodeName&&!t.classList.contains("woltlabHtml")||(this._editor.button.toggle({},"pre","func","block.format"),(t=this._editor.selection.block())&&"PRE"===t.nodeName&&(t.classList.add("woltlabHtml"),1===t.childElementCount&&"BR"===t.children[0].nodeName&&t.removeChild(t.children[0]),this._setTitle(t),t.addEventListener(WCF_CLICK_EVENT,this._callbackEdit),this._editor.caret.end(t)))},_observeLoad:function(){elBySelAll("pre.woltlabHtml",this._editor.$editor[0],function(e){e.addEventListener("mousedown",this._callbackEdit),this._setTitle(e)}.bind(this))},_edit:function(e){var t=e.currentTarget;0===l&&(l=o.getHeight(t));var i=a.offset(t);e.pageY>i.top&&e.pageY<i.top+l&&(e.preventDefault(),this._editor.selection.save(),this._pre=t,console.warn("should edit"))},_setTitle:function(e){["title","description"].forEach(function(t){var n=i.get("wcf.editor.html."+t);elData(e,t)!==n&&elData(e,t,n)})},_delete:function(e){console.warn("should delete"),e.preventDefault();var t=this._pre.nextElementSibling||this._pre.previousElementSibling;null===t&&this._pre.parentNode!==this._editor.core.editor()[0]&&(t=this._pre.parentNode),null===t?(this._editor.code.set(""),this._editor.focus.end()):(elRemove(this._pre),this._editor.caret.end(t)),r.close(this)}},s}),define("WoltLabSuite/Core/Ui/Redactor/Link",["Core","EventKey","Language","Ui/Dialog"],function(e,t,i,n){"use strict";var a=!1,r=null;return{showDialog:function(e){n.open(this),n.setTitle(this,i.get("wcf.editor.link."+(e.insert?"add":"edit")));var t=elById("redactor-modal-button-action");t.textContent=i.get("wcf.global.button."+(e.insert?"insert":"save")),r=e.submitCallback,a||(a=!0,t.addEventListener(WCF_CLICK_EVENT,this._submit.bind(this)))},_submit:function(){if(r())n.close(this);else{var e=elById("redactor-link-url");elInnerError(e,i.get(""===e.value.trim()?"wcf.global.form.error.empty":"wcf.editor.link.error.invalid"))}},_dialogSetup:function(){return{id:"redactorDialogLink",options:{onClose:function(){var e=elById("redactor-link-url"),t=e.nextElementSibling&&"SMALL"===e.nextElementSibling.nodeName?e.nextElementSibling:null;null!==t&&elRemove(t)},onSetup:function(i){var n=elBySel(".formSubmit > .buttonPrimary",i);null!==n&&elBySelAll('input[type="url"], input[type="text"]',i,function(i){i.addEventListener("keyup",function(i){t.Enter(i)&&e.triggerEvent(n,"click")})})},onShow:function(){elById("redactor-link-url").focus()}},source:'<dl><dt><label for="redactor-link-url">'+i.get("wcf.editor.link.url")+'</label></dt><dd><input type="url" id="redactor-link-url" class="long"></dd></dl><dl><dt><label for="redactor-link-url-text">'+i.get("wcf.editor.link.text")+'</label></dt><dd><input type="text" id="redactor-link-url-text" class="long"></dd></dl><div class="formSubmit"><button id="redactor-modal-button-action" class="buttonPrimary"></button></div>'}}}}),define("WoltLabSuite/Core/Ui/Redactor/Mention",["Ajax","Environment","StringUtil","Ui/CloseOverlay"],function(e,t,i,n){"use strict";function a(e){this.init(e)}var r=null;return a.prototype={init:function(e){this._active=!1,this._dropdownActive=!1,this._dropdownMenu=null,this._itemIndex=0,this._lineHeight=null,this._mentionStart="",this._redactor=e,this._timer=null,e.WoltLabEvent.register("keydown",this._keyDown.bind(this)),e.WoltLabEvent.register("keyup",this._keyUp.bind(this)),n.add("UiRedactorMention-"+e.core.element()[0].id,this._hideDropdown.bind(this))},_keyDown:function(e){if(this._dropdownActive){var t=e.event;switch(t.which){case 13:this._setUsername(null,this._dropdownMenu.children[this._itemIndex].children[0]);break;case 38:this._selectItem(-1);break;case 40:this._selectItem(1);break;default:return void this._hideDropdown()}t.preventDefault(),e.cancel=!0}},_keyUp:function(t){var i=t.event;if(13===i.which)return void(this._active=!1);if(!this._dropdownActive||(t.cancel=!0,38!==i.which&&40!==i.which)){var n=this._getTextLineInFrontOfCaret();if(n.length>0&&n.length<25){var a=n.match(/@([^,]{3,})$/);a?a.index&&!n[a.index-1].match(/\s/)||(this._mentionStart=a[1],null!==this._timer&&(window.clearTimeout(this._timer),this._timer=null),this._timer=window.setTimeout(function(){e.api(this,{parameters:{data:{searchString:this._mentionStart}}}),this._timer=null}.bind(this),500)):this._hideDropdown()}else this._hideDropdown()}},_getTextLineInFrontOfCaret:function(){var e=this._selectMention(!1);return null!==e?e.range.cloneContents().textContent.replace(/\u200B/g,"").replace(/\u00A0/g," ").trim():""},_getDropdownMenuPosition:function(){var e=this._selectMention();if(null===e)return null;this._redactor.selection.save(),e.selection.removeAllRanges(),e.selection.addRange(e.range);var t=e.selection.getRangeAt(0).getBoundingClientRect(),i={top:Math.round(t.bottom)+(window.scrollY||window.pageYOffset),left:Math.round(t.left)+document.body.scrollLeft};return null===this._lineHeight&&(this._lineHeight=Math.round(t.bottom-t.top)),this._redactor.selection.restore(),i},_setUsername:function(e,t){e&&(e.preventDefault(),t=e.currentTarget);var i=this._selectMention();if(null===i)return void this._hideDropdown();this._redactor.buffer.set(),i.selection.removeAllRanges(),i.selection.addRange(i.range);var n=getSelection().getRangeAt(0);n.deleteContents(),n.collapse(!0);var a=document.createTextNode("@"+elData(t,"username")+" ");n.insertNode(a),n=document.createRange(),n.selectNode(a),n.collapse(!1),i.selection.removeAllRanges(),i.selection.addRange(n),this._hideDropdown()},_selectMention:function(e){var t=window.getSelection();if(!t.rangeCount||!t.isCollapsed)return null;var i=t.anchorNode;if(i.nodeType===Node.TEXT_NODE&&(i=i.parentNode),-1===i.textContent.indexOf("@"))return null;for(var n=this._redactor.core.editor()[0];i&&i!==n;){if(-1!==["PRE","WOLTLAB-QUOTE"].indexOf(i.nodeName))return null;i=i.parentNode}for(var a=t.getRangeAt(0),r=a.startContainer,o=a.startOffset;r.nodeType===Node.ELEMENT_NODE;){if(0===o&&0===r.childNodes.length)return null;r=r.childNodes[o?o-1:0],o>0&&(o=r.nodeType===Node.TEXT_NODE?r.textContent.length:r.childNodes.length)}for(var s=r,l=-1;null!==s;){if(s.nodeType!==Node.TEXT_NODE)return null;if(-1!==s.textContent.indexOf("@")){l=s.textContent.lastIndexOf("@");break}s=s.previousSibling}if(-1===l)return null;try{a=document.createRange(),a.setStart(s,l),a.setEnd(r,o)}catch(e){return window.console.debug(e),null}if(!1===e){var c="";for(l&&(c=s.textContent.substr(0,l));(s=s.previousSibling)&&s.nodeType===Node.TEXT_NODE;)c=s.textContent+c;if(c.replace(/\u200B/g,"").match(/\S$/))return null}else if(a.cloneContents().textContent.replace(/\u200B/g,"").replace(/\u00A0/g,"").trim().replace(/^@/,"")!==this._mentionStart)return null;return{range:a,selection:t}},_updateDropdownPosition:function(){var e=this._getDropdownMenuPosition();if(null===e)return void this._hideDropdown();e.top+=7,this._dropdownMenu.style.setProperty("left",e.left+"px",""),this._dropdownMenu.style.setProperty("top",e.top+"px",""),this._selectItem(0),e.top+this._dropdownMenu.offsetHeight+10>window.innerHeight+(window.scrollY||window.pageYOffset)&&this._dropdownMenu.style.setProperty("top",e.top-this._dropdownMenu.offsetHeight-2*this._lineHeight+7+"px","")},_selectItem:function(e){var t=elBySel(".active",this._dropdownMenu);null!==t&&t.classList.remove("active"),this._itemIndex+=e,this._itemIndex<0?this._itemIndex=this._dropdownMenu.childElementCount-1:this._itemIndex>=this._dropdownMenu.childElementCount&&(this._itemIndex=0),this._dropdownMenu.children[this._itemIndex].classList.add("active")},_hideDropdown:function(){null!==this._dropdownMenu&&this._dropdownMenu.classList.remove("dropdownOpen"),this._dropdownActive=!1,this._itemIndex=0},_ajaxSetup:function(){return{data:{actionName:"getSearchResultList",className:"wcf\\data\\user\\UserAction",interfaceName:"wcf\\data\\ISearchAction",parameters:{data:{includeUserGroups:!0,scope:"mention"}}},silent:!0}},_ajaxSuccess:function(e){if(!Array.isArray(e.returnValues)||!e.returnValues.length)return void this._hideDropdown();null===this._dropdownMenu&&(this._dropdownMenu=elCreate("ol"),this._dropdownMenu.className="dropdownMenu",null===r&&(r=elCreate("div"),r.className="dropdownMenuContainer",document.body.appendChild(r)),r.appendChild(this._dropdownMenu)),this._dropdownMenu.innerHTML="";for(var t,n,a,o=this._setUsername.bind(this),s=0,l=e.returnValues.length;s<l;s++)a=e.returnValues[s],n=elCreate("li"),t=elCreate("a"),t.addEventListener("mousedown",o),t.className="box16",t.innerHTML="<span>"+a.icon+"</span> <span>"+i.escapeHTML(a.label)+"</span>",elData(t,"user-id",a.objectID),elData(t,"username",a.label),n.appendChild(t),this._dropdownMenu.appendChild(n);this._dropdownMenu.classList.add("dropdownOpen"),this._dropdownActive=!0,this._updateDropdownPosition()}},a}),define("WoltLabSuite/Core/Ui/Redactor/Page",["WoltLabSuite/Core/Ui/Page/Search"],function(e){"use strict";function t(e,t){this.init(e,t)}return t.prototype={init:function(e,t){this._editor=e,t.addEventListener(WCF_CLICK_EVENT,this._click.bind(this))},_click:function(t){t.preventDefault(),e.open(this._insert.bind(this))},_insert:function(e){this._editor.buffer.set(),this._editor.insert.text("[wsp='"+e+"'][/wsp]")}},t}),define("WoltLabSuite/Core/Ui/Redactor/Quote",["Core","EventHandler","EventKey","Language","StringUtil","Dom/Util","Ui/Dialog","./Metacode","./PseudoHeader"],function(e,t,i,n,a,r,o,s,l){"use strict";function c(e,t){this.init(e,t)}var d=0;return c.prototype={init:function(e,i){this._quote=null,this._quotes=elByTag("woltlab-quote",e.$editor[0]),this._editor=e,this._elementId=this._editor.$element[0].id,t.add("com.woltlab.wcf.redactor2","observe_load_"+this._elementId,this._observeLoad.bind(this)),this._editor.button.addCallback(i,this._click.bind(this)),this._callbackEdit=this._edit.bind(this),this._observeLoad(),t.add("com.woltlab.wcf.redactor2","insertQuote_"+this._elementId,this._insertQuote.bind(this))},_insertQuote:function(e){if(!this._editor.WoltLabSource.isActive()){t.fire("com.woltlab.wcf.redactor2","showEditor");var i=this._editor.core.editor()[0];this._editor.selection.restore(),this._editor.buffer.set();var n=this._editor.selection.block();for(!1===n&&(this._editor.focus.end(),n=this._editor.selection.block());n&&n.parentNode!==i;)n=n.parentNode;var r=elCreate("woltlab-quote");elData(r,"author",e.author),elData(r,"link",e.link);var o=e.content;e.isText?(o=a.escapeHTML(o),o="<p>"+o+"</p>",o=o.replace(/\n\n/g,"</p><p>"),o=o.replace(/\n/g,"<br>")):o=s.convertFromHtml(this._editor.$element[0].id,o),r.innerHTML=o,n.parentNode.insertBefore(r,n.nextSibling),"P"!==n.nodeName||"<br>"!==n.innerHTML&&""!==n.innerHTML.replace(/\u200B/g,"")||n.parentNode.removeChild(n);var l=r.previousElementSibling;l&&"P"!==l.nodeName&&(l=elCreate("p"),l.textContent="​",r.parentNode.insertBefore(l,r)),this._editor.WoltLabCaret.paragraphAfterBlock(r),this._editor.buffer.set()}},_click:function(){this._editor.button.toggle({},"woltlab-quote","func","block.format");var e=this._editor.selection.block();e&&"WOLTLAB-QUOTE"===e.nodeName&&(this._setTitle(e),e.addEventListener(WCF_CLICK_EVENT,this._callbackEdit),this._editor.caret.end(e))},_observeLoad:function(){for(var e,t=0,i=this._quotes.length;t<i;t++)e=this._quotes[t],e.addEventListener("mousedown",this._callbackEdit),this._setTitle(e)},_edit:function(e){var t=e.currentTarget;0===d&&(d=l.getHeight(t));var i=r.offset(t);e.pageY>i.top&&e.pageY<i.top+d&&(e.preventDefault(),this._editor.selection.save(),this._quote=t,o.open(this))},_dialogSubmit:function(){var e="redactor-quote-"+this._elementId,t=elById(e+"-url"),i=t.value.replace(/\u200B/g,"").trim();if(i.length&&!/^https?:\/\/[^\/]+/.test(i))return void elInnerError(t,n.get("wcf.editor.quote.url.error.invalid"));elInnerError(t,!1),elData(this._quote,"author",elById(e+"-author").value),elData(this._quote,"link",i),this._setTitle(this._quote),this._editor.caret.after(this._quote),o.close(this)},_setTitle:function(e){var t=n.get("wcf.editor.quote.title",{author:elData(e,"author"),url:elData(e,"url")});elData(e,"title")!==t&&elData(e,"title",t)},_delete:function(e){e.preventDefault();var t=this._quote.nextElementSibling||this._quote.previousElementSibling;null===t&&this._quote.parentNode!==this._editor.core.editor()[0]&&(t=this._quote.parentNode),null===t?(this._editor.code.set(""),this._editor.focus.end()):(elRemove(this._quote),this._editor.caret.end(t)),o.close(this)},_dialogSetup:function(){var e="redactor-quote-"+this._elementId,t=e+"-author",i=e+"-button-delete",a=e+"-button-save",r=e+"-url";return{id:e,options:{onClose:function(){this._editor.selection.restore(),o.destroy(this)}.bind(this),onSetup:function(){elById(i).addEventListener(WCF_CLICK_EVENT,this._delete.bind(this))}.bind(this),onShow:function(){elById(t).value=elData(this._quote,"author"),elById(r).value=elData(this._quote,"link")}.bind(this),title:n.get("wcf.editor.quote.edit")},source:'<div class="section"><dl><dt><label for="'+t+'">'+n.get("wcf.editor.quote.author")+'</label></dt><dd><input type="text" id="'+t+'" class="long" data-dialog-submit-on-enter="true"></dd></dl><dl><dt><label for="'+r+'">'+n.get("wcf.editor.quote.url")+'</label></dt><dd><input type="text" id="'+r+'" class="long" data-dialog-submit-on-enter="true"><small>'+n.get("wcf.editor.quote.url.description")+'</small></dd></dl></div><div class="formSubmit"><button id="'+a+'" class="buttonPrimary" data-type="submit">'+n.get("wcf.global.button.save")+'</button><button id="'+i+'">'+n.get("wcf.global.button.delete")+"</button></div>"}}},c}),define("WoltLabSuite/Core/Ui/Redactor/Spoiler",["EventHandler","EventKey","Language","StringUtil","Dom/Util","Ui/Dialog","./PseudoHeader"],function(e,t,i,n,a,r,o){"use strict";function s(e){this.init(e)}var l=0;return s.prototype={init:function(t){this._editor=t,this._elementId=this._editor.$element[0].id,this._spoiler=null,e.add("com.woltlab.wcf.redactor2","bbcode_spoiler_"+this._elementId,this._bbcodeSpoiler.bind(this)),e.add("com.woltlab.wcf.redactor2","observe_load_"+this._elementId,this._observeLoad.bind(this)),this._callbackEdit=this._edit.bind(this),this._observeLoad()},_bbcodeSpoiler:function(e){e.cancel=!0,this._editor.button.toggle({},"woltlab-spoiler","func","block.format");var t=this._editor.selection.block();t&&"WOLTLAB-SPOILER"===t.nodeName&&(this._setTitle(t),t.addEventListener(WCF_CLICK_EVENT,this._callbackEdit),this._editor.caret.end(t))},_observeLoad:function(){elBySelAll("woltlab-spoiler",this._editor.$editor[0],function(e){e.addEventListener("mousedown",this._callbackEdit),this._setTitle(e)}.bind(this))},_edit:function(e){var t=e.currentTarget;0===l&&(l=o.getHeight(t));var i=a.offset(t);e.pageY>i.top&&e.pageY<i.top+l&&(e.preventDefault(),this._editor.selection.save(),this._spoiler=t,r.open(this))},_dialogSubmit:function(){elData(this._spoiler,"label",elById("redactor-spoiler-"+this._elementId+"-label").value),this._setTitle(this._spoiler),this._editor.caret.after(this._spoiler),r.close(this)},_setTitle:function(e){var t=i.get("wcf.editor.spoiler.title",{label:elData(e,"label")});elData(e,"title")!==t&&elData(e,"title",t)},_delete:function(e){e.preventDefault();var t=this._spoiler.nextElementSibling||this._spoiler.previousElementSibling;null===t&&this._spoiler.parentNode!==this._editor.core.editor()[0]&&(t=this._spoiler.parentNode),null===t?(this._editor.code.set(""),this._editor.focus.end()):(elRemove(this._spoiler),this._editor.caret.end(t)),r.close(this)},_dialogSetup:function(){var e="redactor-spoiler-"+this._elementId,t=e+"-button-delete",n=e+"-button-save",a=e+"-label";return{id:e,options:{onClose:function(){this._editor.selection.restore(),r.destroy(this)}.bind(this),onSetup:function(){elById(t).addEventListener(WCF_CLICK_EVENT,this._delete.bind(this))}.bind(this),onShow:function(){elById(a).value=elData(this._spoiler,"label")}.bind(this),title:i.get("wcf.editor.spoiler.edit")},
+source:'<div class="section"><dl><dt><label for="'+a+'">'+i.get("wcf.editor.spoiler.label")+'</label></dt><dd><input type="text" id="'+a+'" class="long" data-dialog-submit-on-enter="true"><small>'+i.get("wcf.editor.spoiler.label.description")+'</small></dd></dl></div><div class="formSubmit"><button id="'+n+'" class="buttonPrimary" data-type="submit">'+i.get("wcf.global.button.save")+'</button><button id="'+t+'">'+i.get("wcf.global.button.delete")+"</button></div>"}}},s}),define("WoltLabSuite/Core/Ui/Redactor/Table",["Language","Ui/Dialog"],function(e,t){"use strict";var i=null;return{showDialog:function(e){t.open(this),i=e.submitCallback},_dialogSubmit:function(){var e=!0;["rows","cols"].forEach(function(t){var i=elById("redactor-table-"+t);(i.value<1||i.value>100)&&(e=!1)}),e&&(i(),t.close(this))},_dialogSetup:function(){return{id:"redactorDialogTable",options:{onShow:function(){elById("redactor-table-rows").value=2,elById("redactor-table-cols").value=3},title:e.get("wcf.editor.table.insertTable")},source:'<dl><dt><label for="redactor-table-rows">'+e.get("wcf.editor.table.rows")+'</label></dt><dd><input type="number" id="redactor-table-rows" class="small" min="1" max="100" value="2" data-dialog-submit-on-enter="true"></dd></dl><dl><dt><label for="redactor-table-cols">'+e.get("wcf.editor.table.cols")+'</label></dt><dd><input type="number" id="redactor-table-cols" class="small" min="1" max="100" value="3" data-dialog-submit-on-enter="true"></dd></dl><div class="formSubmit"><button id="redactor-modal-button-action" class="buttonPrimary" data-type="submit">'+e.get("wcf.global.button.insert")+"</button></div>"}}}}),define("WoltLabSuite/Core/Ui/Search/Page",["Core","Dom/Traverse","Dom/Util","Ui/Screen","Ui/SimpleDropdown","./Input"],function(e,t,i,n,a,r){"use strict";return{init:function(o){var s=elById("pageHeaderSearchInput");new r(s,{ajax:{className:"wcf\\data\\search\\keyword\\SearchKeywordAction"},callbackDropdownInit:function(e){if(e.classList.add("dropdownMenuPageSearch"),n.is("screen-lg")){elData(e,"dropdown-alignment-horizontal","right");var t=s.clientWidth;e.style.setProperty("min-width",t+"px","");var a=s.parentNode,r=i.offset(a).left+a.clientWidth-(i.offset(s).left+t),o=i.styleAsInt(window.getComputedStyle(a),"padding-bottom");e.style.setProperty("transform","translateX(-"+Math.ceil(r)+"px) translateY(-"+o+"px)","")}},callbackSelect:function(){return setTimeout(function(){t.parentByTag(s,"FORM").submit()},1),!0}});var l=a.getDropdownMenu(i.identify(elBySel(".pageHeaderSearchType"))),c=this._click.bind(this);elBySelAll("a[data-object-type]",l,function(e){e.addEventListener(WCF_CLICK_EVENT,c)});var d=elBySel('a[data-object-type="'+o+'"]',l);e.triggerEvent(d,WCF_CLICK_EVENT)},_click:function(e){e.preventDefault();var t=elById("pageHeader");t.classList.add("searchBarForceOpen"),window.setTimeout(function(){t.classList.remove("searchBarForceOpen")},10);var i=elData(e.currentTarget,"object-type"),n=elById("pageHeaderSearchParameters");n.innerHTML="";var a=elData(e.currentTarget,"extended-link");a&&(elBySel(".pageHeaderSearchExtendedLink").href=a);var r=elData(e.currentTarget,"parameters");r=r?JSON.parse(r):{},i&&(r["types[]"]=i);for(var o in r)if(r.hasOwnProperty(o)){var s=elCreate("input");s.type="hidden",s.name=o,s.value=r[o],n.appendChild(s)}elBySel(".pageHeaderSearchType > .button > .pageHeaderSearchTypeLabel",elById("pageHeaderSearchInputContainer")).textContent=e.currentTarget.textContent}}}),define("WoltLabSuite/Core/Ui/Smiley/Insert",["EventHandler","EventKey"],function(e,t){"use strict";function i(e){this.init(e)}return i.prototype={_editorId:"",init:function(e){this._editorId=e;var t=elById("smilies-"+this._editorId);if(!t&&!(t=elById(this._editorId+"SmiliesTabContainer")))throw new Error("Unable to find the message tab menu container containing the smilies.");t.addEventListener("keydown",this._keydown.bind(this)),t.addEventListener("mousedown",this._mousedown.bind(this))},_keydown:function(e){var i=document.activeElement;if(i.classList.contains("jsSmiley"))if(t.ArrowLeft(e)||t.ArrowRight(e)||t.Home(e)||t.End(e)){e.preventDefault();var n=Array.prototype.slice.call(elBySelAll(".jsSmiley",e.currentTarget));t.ArrowLeft(e)&&n.reverse();var a=n.indexOf(i);t.Home(e)?a=0:t.End(e)?a=n.length-1:(a+=1)===n.length&&(a=0),n[a].focus()}else(t.Enter(e)||t.Space(e))&&(e.preventDefault(),this._insert(elBySel("img",i)))},_mousedown:function(e){e.preventDefault();var t=e.target.closest("li"),i=elBySel("img",t);i&&this._insert(i)},_insert:function(t){e.fire("com.woltlab.wcf.redactor2","insertSmiley_"+this._editorId,{img:t})}},i}),define("WoltLabSuite/Core/Ui/Style/FontAwesome",["Language","Ui/Dialog","WoltLabSuite/Core/Ui/ItemList/Filter"],function(e,t,i){"use strict";var n,a,r,o=[];return{setup:function(e){o=e},open:function(e){if(0===o.length)throw new Error("Missing icon data, please include the template before calling this method using `{include file='fontAwesomeJavaScript'}`.");n=e,t.open(this)},_click:function(e){e.preventDefault();var i=e.target.closest("li"),a=elBySel("small",i).textContent.trim();t.close(this),n(a)},_dialogSetup:function(){return{id:"fontAwesomeSelection",options:{onSetup:function(){a=elById("fontAwesomeIcons");for(var e,t="",n=0,s=o.length;n<s;n++)e=o[n],t+='<li><span class="icon icon48 fa-'+e+'"></span><small>'+e+"</small></li>";a.innerHTML=t,a.addEventListener(WCF_CLICK_EVENT,this._click.bind(this)),r=new i("fontAwesomeIcons",{callbackPrepareItem:function(e){var t=elBySel("small",e);return{item:e,span:t,text:t.textContent.trim()}},enableVisibilityFilter:!1})}.bind(this),onShow:function(){r.reset()},title:e.get("wcf.global.fontAwesome.selectIcon")},source:'<ul class="fontAwesomeIcons" id="fontAwesomeIcons"></ul>'}}}}),define("WoltLabSuite/Core/Ui/Toggle/Input",["Core"],function(e){"use strict";function t(e,t){this.init(e,t)}return t.prototype={init:function(t,i){if(this._element=elBySel(t),null===this._element)throw new Error("Unable to find element by selector '"+t+"'.");var n="INPUT"===this._element.nodeName?elAttr(this._element,"type"):"";if("checkbox"!==n&&"radio"!==n)throw new Error("Illegal element, expected input[type='checkbox'] or input[type='radio'].");this._options=e.extend({hide:[],show:[]},i),["hide","show"].forEach(function(e){var t,i,n;for(i=0,n=this._options[e].length;i<n;i++)if("string"!=typeof(t=this._options[e][i])&&!(t instanceof Element))throw new TypeError("The array '"+e+"' may only contain string selectors or DOM elements.")}.bind(this)),this._element.addEventListener("change",this._change.bind(this)),this._handleElements(this._options.show,this._element.checked),this._handleElements(this._options.hide,!this._element.checked)},_change:function(e){var t=e.currentTarget.checked;this._handleElements(this._options.show,t),this._handleElements(this._options.hide,!t)},_handleElements:function(e,t){for(var i,n,a=0,r=e.length;a<r;a++){if("string"==typeof(i=e[a])){if(null===(n=elBySel(i)))throw new Error("Unable to find element by selector '"+i+"'.");e[a]=i=n}window[t?"elShow":"elHide"](i)}}},t}),define("WoltLabSuite/Core/Ui/User/Editor",["Ajax","Language","StringUtil","Dom/Util","Ui/Dialog","Ui/Notification"],function(e,t,i,n,a,r){"use strict";var o="",s=null;return{init:function(){s=elBySel(".userProfileUser"),["ban","disableAvatar","disableCoverPhoto","disableSignature","enable"].forEach(function(e){var t=elBySel(".userProfileButtonMenu .jsButtonUser"+i.ucfirst(e));t&&(elData(t,"action",e),t.addEventListener(WCF_CLICK_EVENT,this._click.bind(this)))}.bind(this))},_click:function(t){t.preventDefault();var i=elData(t.currentTarget,"action"),n="";switch(i){case"ban":elDataBool(s,"banned")&&(n="unban");break;case"disableAvatar":elDataBool(s,"disable-avatar")&&(n="enableAvatar");break;case"disableCoverPhoto":elDataBool(s,"disable-cover-photo")&&(n="enableCoverPhoto");break;case"disableSignature":elDataBool(s,"disable-signature")&&(n="enableSignature");break;case"enable":n=elDataBool(s,"is-disabled")?"enable":"disable"}""===n?(o=i,a.open(this)):e.api(this,{actionName:n})},_submit:function(i){i.preventDefault();var n=elById("wcfUiUserEditorExpiresLabel"),a="",r="";elById("wcfUiUserEditorNeverExpires").checked||""===(a=elById("wcfUiUserEditorExpiresDatePicker").value)&&(r=t.get("wcf.global.form.error.empty")),elInnerError(n,r);var s={};s[o+"Expires"]=a,s[o+"Reason"]=elById("wcfUiUserEditorReason").value.trim(),e.api(this,{actionName:o,parameters:s})},_ajaxSuccess:function(e){switch(e.actionName){case"ban":case"unban":elData(s,"banned","ban"===e.actionName),elBySel(".userProfileButtonMenu .jsButtonUserBan").textContent=t.get("wcf.user."+("ban"===e.actionName?"unban":"ban"));var i=elBySel(".contentTitle",s),n=elBySel(".jsUserBanned",i);"ban"===e.actionName?(n=elCreate("span"),n.className="icon icon24 fa-lock jsUserBanned jsTooltip",n.title=e.returnValues,i.appendChild(n)):n&&elRemove(n);break;case"disableAvatar":case"enableAvatar":elData(s,"disable-avatar","disableAvatar"===e.actionName),elBySel(".userProfileButtonMenu .jsButtonUserDisableAvatar").textContent=t.get("wcf.user."+("disableAvatar"===e.actionName?"enable":"disable")+"Avatar");break;case"disableCoverPhoto":case"enableCoverPhoto":elData(s,"disable-cover-photo","disableCoverPhoto"===e.actionName),elBySel(".userProfileButtonMenu .jsButtonUserDisableCoverPhoto").textContent=t.get("wcf.user."+("disableCoverPhoto"===e.actionName?"enable":"disable")+"CoverPhoto");break;case"disableSignature":case"enableSignature":elData(s,"disable-signature","disableSignature"===e.actionName),elBySel(".userProfileButtonMenu .jsButtonUserDisableSignature").textContent=t.get("wcf.user."+("disableSignature"===e.actionName?"enable":"disable")+"Signature");break;case"enable":case"disable":elData(s,"is-disabled","disable"===e.actionName),elBySel(".userProfileButtonMenu .jsButtonUserEnable").textContent=t.get("wcf.acp.user."+("enable"===e.actionName?"disable":"enable"))}"ban"!==e.actionName&&"disableAvatar"!==e.actionName&&"disableCoverPhoto"!==e.actionName&&"disableSignature"!==e.actionName||a.close(this),r.show()},_ajaxSetup:function(){return{data:{className:"wcf\\data\\user\\UserAction",objectIDs:[elData(s,"object-id")]}}},_dialogSetup:function(){return{id:"wcfUiUserEditor",options:{onSetup:function(e){elById("wcfUiUserEditorNeverExpires").addEventListener("change",function(){window[this.checked?"elHide":"elShow"](elById("wcfUiUserEditorExpiresSettings"))}),elBySel("button.buttonPrimary",e).addEventListener(WCF_CLICK_EVENT,this._submit.bind(this))}.bind(this),onShow:function(e){a.setTitle("wcfUiUserEditor",t.get("wcf.user."+o+".confirmMessage"));var i=elById("wcfUiUserEditorReason").nextElementSibling,n="wcf.user."+o+".reason.description";i.textContent=t.get(n),window[i.textContent===n?"elHide":"elShow"](i),i=elById("wcfUiUserEditorNeverExpires").nextElementSibling,i.textContent=t.get("wcf.user."+o+".neverExpires"),i=elBySel('label[for="wcfUiUserEditorExpires"]',e),i.textContent=t.get("wcf.user."+o+".expires"),i=elById("wcfUiUserEditorExpiresLabel"),i.textContent=t.get("wcf.user."+o+".expires.description")}},source:'<div class="section"><dl><dt><label for="wcfUiUserEditorReason">'+t.get("wcf.global.reason")+'</label></dt><dd><textarea id="wcfUiUserEditorReason" cols="40" rows="3"></textarea><small></small></dd></dl><dl><dt></dt><dd><label><input type="checkbox" id="wcfUiUserEditorNeverExpires" checked> <span></span></label></dd></dl><dl id="wcfUiUserEditorExpiresSettings" style="display: none"><dt><label for="wcfUiUserEditorExpires"></label></dt><dd><input type="date" name="wcfUiUserEditorExpires" id="wcfUiUserEditorExpires" class="medium" min="'+new Date(1e3*TIME_NOW).toISOString()+'" data-ignore-timezone="true"><small id="wcfUiUserEditorExpiresLabel"></small></dd></dl></div><div class="formSubmit"><button class="buttonPrimary">'+t.get("wcf.global.button.submit")+"</button></div>"}}}}),define("WoltLabSuite/Core/Controller/Condition/Page/Dependence",["Dom/ChangeListener","Dom/Traverse","EventHandler","ObjectMap"],function(e,t,i,n){"use strict";var a=elBySelAll('input[name="pageIDs[]"]'),r=[],o=new n,s=new n,l=!1;return{register:function(e,i){if(r.push(e),o.set(e,i),s.set(e,[]),!l){for(var n=0,c=a.length;n<c;n++)a[n].addEventListener("change",this._checkVisibility.bind(this));l=!0}t.parentByTag(e,"FORM").addEventListener("submit",function(){"none"===e.style.getPropertyValue("display")&&e.remove()}),this._checkVisibility()},_checkVisibility:function(){for(var e,t,n,s,l,c=0,d=r.length;c<d;c++){e=r[c],n=o.get(e),s=[];for(var u=0,h=a.length;u<h;u++)t=a[u],t.checked&&s.push(~~t.value);l=s.filter(function(e){return-1===n.indexOf(e)}),!s.length||l.length?this._hideDependentElement(e):this._showDependentElement(e)}i.fire("com.woltlab.wcf.pageConditionDependence","checkVisivility")},_hideDependentElement:function(e){elHide(e);for(var t=s.get(e),i=0,n=t.length;i<n;i++)elHide(t[i]);s.set(e,[])},_showDependentElement:function(e){elShow(e);for(var t=e;(t=t.parentNode)&&t instanceof Element;)"none"===t.style.getPropertyValue("display")&&s.get(e).push(t),elShow(t)}}}),define("WoltLabSuite/Core/Controller/Map/Route/Planner",["Dom/Traverse","Dom/Util","Language","Ui/Dialog","WoltLabSuite/Core/Ajax/Status"],function(e,t,i,n,a){function r(e,t){if(this._button=elById(e),null===this._button)throw new Error("Unknown button with id '"+e+"'");this._button.addEventListener("click",this._openDialog.bind(this)),this._destination=t}return r.prototype={_dialogSetup:function(){return{id:this._button.id+"Dialog",options:{onShow:this._initDialog.bind(this),title:i.get("wcf.map.route.planner")},source:'<div class="googleMapsDirectionsContainer" style="display: none;"><div class="googleMap"></div><div class="googleMapsDirections"></div></div><small class="googleMapsDirectionsGoogleLinkContainer"><a href="'+this._getGoogleMapsLink()+'" class="googleMapsDirectionsGoogleLink" target="_blank" style="display: none;">'+i.get("wcf.map.route.viewOnGoogleMaps")+"</a></small><dl><dt>"+i.get("wcf.map.route.origin")+'</dt><dd><input type="text" name="origin" class="long" autofocus /></dd></dl><dl style="display: none;"><dt>'+i.get("wcf.map.route.travelMode")+'</dt><dd><select name="travelMode"><option value="driving">'+i.get("wcf.map.route.travelMode.driving")+'</option><option value="walking">'+i.get("wcf.map.route.travelMode.walking")+'</option><option value="bicycling">'+i.get("wcf.map.route.travelMode.bicycling")+'</option><option value="transit">'+i.get("wcf.map.route.travelMode.transit")+"</option></select></dd></dl>"}},_calculateRoute:function(e){var t=n.getDialog(this).dialog;e.label&&(this._originInput.value=e.label),void 0===this._map&&(this._map=new google.maps.Map(elByClass("googleMap",t)[0],{disableDoubleClickZoom:WCF.Location.GoogleMaps.Settings.get("disableDoubleClickZoom"),draggable:WCF.Location.GoogleMaps.Settings.get("draggable"),mapTypeId:google.maps.MapTypeId.ROADMAP,scaleControl:WCF.Location.GoogleMaps.Settings.get("scaleControl"),scrollwheel:WCF.Location.GoogleMaps.Settings.get("scrollwheel")}),this._directionsService=new google.maps.DirectionsService,this._directionsRenderer=new google.maps.DirectionsRenderer,this._directionsRenderer.setMap(this._map),this._directionsRenderer.setPanel(elByClass("googleMapsDirections",t)[0]),this._googleLink=elByClass("googleMapsDirectionsGoogleLink",t)[0]);var i={destination:this._destination,origin:e.location,provideRouteAlternatives:!0,travelMode:google.maps.TravelMode[this._travelMode.value.toUpperCase()]};a.show(),this._directionsService.route(i,this._setRoute.bind(this)),elAttr(this._googleLink,"href",this._getGoogleMapsLink(e.location,this._travelMode.value)),this._lastOrigin=e.location},_getGoogleMapsLink:function(e,t){if(e){var i="https://www.google.com/maps/dir/?api=1&origin="+e.lat()+","+e.lng()+"&destination="+this._destination.lat()+","+this._destination.lng();return t&&(i+="&travelmode="+t),i}return"https://www.google.com/maps/search/?api=1&query="+this._destination.lat()+","+this._destination.lng()},_initDialog:function(){if(!this._didInitDialog){var e=n.getDialog(this).dialog;this._originInput=elBySel('input[name="origin"]',e),new WCF.Location.GoogleMaps.LocationSearch(this._originInput,this._calculateRoute.bind(this)),this._travelMode=elBySel('select[name="travelMode"]',e),this._travelMode.addEventListener("change",this._updateRoute.bind(this)),this._didInitDialog=!0}},_openDialog:function(){n.open(this)},_setRoute:function(t,n){a.hide(),"OK"===n?(elShow(this._map.getDiv().parentNode),google.maps.event.trigger(this._map,"resize"),this._directionsRenderer.setDirections(t),elShow(e.parentByTag(this._travelMode,"DL")),elShow(this._googleLink),elInnerError(this._originInput,!1)):("OVER_QUERY_LIMIT"!==n&&"REQUEST_DENIED"!==n&&(n="NOT_FOUND"),elInnerError(this._originInput,i.get("wcf.map.route.error."+n.toLowerCase())))},_updateRoute:function(){this._calculateRoute({location:this._lastOrigin})}},r}),define("WoltLabSuite/Core/Controller/User/Notification/Settings",["Dictionary","Language","Dom/Traverse","Ui/SimpleDropdown"],function(e,t,i,n){"use strict";var a=new e,r=null,o=null;return{setup:function(){r=this._click.bind(this),o=this._selectType.bind(this);for(var e,t,i=elBySelAll("#notificationSettings .flexibleButtonGroup"),n=0,a=i.length;n<a;n++)e=i[n],null!==(t=elBySel(".notificationSettingsEmail",e))&&this._initGroup(e,t)},_initGroup:function(e,t){var n=~~elData(e,"object-id");elById("settings_"+n+"_disabled").addEventListener(WCF_CLICK_EVENT,function(){t.classList.remove("active")}),elById("settings_"+n+"_enabled").addEventListener(WCF_CLICK_EVENT,function(){t.classList.add("active")});var o=i.childByTag(t,"INPUT"),s=i.childByTag(t,"A");elData(s,"object-id",n),s.addEventListener(WCF_CLICK_EVENT,r),a.set(n,{button:s,dropdownMenu:null,mailSetting:t,mailValue:o})},_click:function(e){e.preventDefault();var t=e.currentTarget,r=~~elData(t,"object-id"),o=a.get(r);if(null===o.dropdownMenu)o.dropdownMenu=this._createDropdown(r,o.mailValue.value),t.parentNode.classList.add("dropdown"),t.parentNode.appendChild(o.dropdownMenu),n.init(t,e);else for(var s=i.childrenByTag(o.dropdownMenu,"LI"),l=o.mailValue.value,c=0;c<4;c++)s[c].classList[elData(s[c],"value")===l?"add":"remove"]("active")},_createDropdown:function(e,i){var n=elCreate("ul");n.className="dropdownMenu",elData(n,"object-id",e);for(var a,r,s,l=["instant","daily","divider","none"],c=0;c<4;c++)s=l[c],r=elCreate("li"),"divider"===s?r.className="dropdownDivider":(a=elCreate("a"),a.textContent=t.get("wcf.user.notification.mailNotificationType."+s),r.appendChild(a),elData(r,"value",s),r.addEventListener(WCF_CLICK_EVENT,o),i===s&&(r.className="active")),n.appendChild(r);return n},_selectType:function(e){var i=elData(e.currentTarget,"value"),n=~~elData(e.currentTarget.parentNode,"object-id"),r=a.get(n);r.mailValue.value=i,elBySel("span.title",r.mailSetting).textContent=t.get("wcf.user.notification.mailNotificationType."+i),r.button.classList["none"===i?"remove":"add"]("yellow"),r.button.classList["none"===i?"remove":"add"]("active")}}}),define("WoltLabSuite/Core/Form/Builder/Field/Captcha",["Core","./Field","WoltLabSuite/Core/Controller/Captcha"],function(e,t,i){"use strict";function n(e){this.init(e)}return e.inherit(n,t,{_getData:function(){return i.has(this._fieldId)?i.getData(this._fieldId):{}},_readField:function(){},destroy:function(){i.has(this._fieldId)&&i.delete(this._fieldId)}}),n}),define("WoltLabSuite/Core/Form/Builder/Field/Checkboxes",["Core","./Field"],function(e,t){"use strict";function i(e){this.init(e)}return e.inherit(i,t,{_getData:function(){var e={};e[this._fieldId]=[];for(var t=0,i=this._fields.length;t<i;t++)this._fields[t].checked&&e[this._fieldId].push(this._fields[t].value);return e},_readField:function(){this._fields=elBySelAll('input[name="'+this._fieldId+'[]"]')}}),i}),define("WoltLabSuite/Core/Form/Builder/Field/Checked",["Core","./Field"],function(e,t){"use strict";function i(e){this.init(e)}return e.inherit(i,t,{_getData:function(){var e={};return e[this._fieldId]=~~this._field.checked,e}}),i}),define("WoltLabSuite/Core/Form/Builder/Field/Date",["Core","WoltLabSuite/Core/Date/Picker","./Field"],function(e,t,i){"use strict";function n(e){this.init(e)}return e.inherit(n,i,{_getData:function(){var e={};return e[this._fieldId]=t.getValue(this._field),e}}),n}),define("WoltLabSuite/Core/Form/Builder/Field/ItemList",["Core","./Field","WoltLabSuite/Core/Ui/ItemList/Static"],function(e,t,i){"use strict";function n(e){this.init(e)}return e.inherit(n,t,{_getData:function(){var e={};e[this._fieldId]=[];for(var t=i.getValues(this._fieldId),n=0,a=t.length;n<a;n++)t[n].objectId?e[this._fieldId][t[n].objectId]=t[n].value:e[this._fieldId].push(t[n].value);return e}}),n}),define("WoltLabSuite/Core/Form/Builder/Field/RadioButton",["Core","./Field"],function(e,t){"use strict";function i(e){this.init(e)}return e.inherit(i,t,{_getData:function(){for(var e={},t=0,i=this._fields.length;t<i;t++)if(this._fields[t].checked){e[this._fieldId]=this._fields[t].value;break}return e},_readField:function(){this._fields=elBySelAll("input[name="+this._fieldId+"]")}}),i}),define("WoltLabSuite/Core/Form/Builder/Field/SimpleAcl",["Core","./Field"],function(e,t){"use strict";function i(e){this.init(e)}return e.inherit(i,t,{_getData:function(){var e=[];elBySelAll('input[name="'+this._fieldId+'[group][]"]',void 0,function(t){e.push(~~t.value)});var t=[];elBySelAll('input[name="'+this._fieldId+'[user][]"]',void 0,function(e){t.push(~~e.value)});var i={};return i[this._fieldId]={group:e,user:t},i},_readField:function(){}}),i}),define("WoltLabSuite/Core/Form/Builder/Field/Tag",["Core","./Field","WoltLabSuite/Core/Ui/ItemList"],function(e,t,i){"use strict";function n(e){this.init(e)}return e.inherit(n,t,{_getData:function(){var e={};e[this._fieldId]=[];for(var t=i.getValues(this._fieldId),n=0,a=t.length;n<a;n++)e[this._fieldId].push(t[n].value);return e}}),n}),define("WoltLabSuite/Core/Form/Builder/Field/User",["Core","./Field","WoltLabSuite/Core/Ui/ItemList"],function(e,t,i){"use strict";function n(e){this.init(e)}return e.inherit(n,t,{_getData:function(){for(var e=i.getValues(this._fieldId),t=[],n=0,a=e.length;n<a;n++)e[n].objectId&&t.push(e[n].value);var r={};return r[this._fieldId]=t.join(","),r}}),n}),define("WoltLabSuite/Core/Form/Builder/Field/Value",["Core","./Field"],function(e,t){"use strict";function i(e){this.init(e)}return e.inherit(i,t,{_getData:function(){var e={};return e[this._fieldId]=this._field.value,e}}),i}),define("WoltLabSuite/Core/Form/Builder/Field/ValueI18n",["Core","./Field","WoltLabSuite/Core/Language/Input"],function(e,t,i){"use strict";function n(e){this.init(e)}return e.inherit(n,t,{_getData:function(){var e={},t=i.getValues(this._fieldId);return t.size>1?e[this._fieldId+"_i18n"]=t.toObject():e[this._fieldId]=t.get(0),e},destroy:function(){i.unregister(this._fieldId)}}),n}),define("WoltLabSuite/Core/Ui/Comment/Response/Add",["Core","Language","Dom/ChangeListener","Dom/Util","Dom/Traverse","Ui/Notification","WoltLabSuite/Core/Ui/Comment/Add"],function(e,t,i,n,a,r,o){"use strict";function s(e,t){this.init(e,t)}return e.inherit(s,o,{init:function(t,i){s._super.prototype.init.call(this,t),this._options=e.extend({callbackInsert:null},i)},getContainer:function(){return this._isBusy?null:this._container},getContent:function(){return window.jQuery(this._textarea).redactor("code.get")},setContent:function(e){window.jQuery(this._textarea).redactor("code.set",e),window.jQuery(this._textarea).redactor("WoltLabCaret.endOfEditor");var t=elBySel(".innerError",this._textarea.parentNode);null!==t&&elRemove(t),this._content.classList.remove("collapsed"),this._focusEditor()},_getParameters:function(){var e=s._super.prototype._getParameters.call(this);return e.data.commentID=~~elData(this._container.closest(".comment"),"object-id"),e},_insertMessage:function(e){var o=a.childByClass(this._container.parentNode,"commentContent"),s=o.nextElementSibling;return null!==s&&s.classList.contains("commentResponseList")||(s=elCreate("ul"),s.className="containerList commentResponseList",elData(s,"responses",0),o.parentNode.insertBefore(s,o.nextSibling)),n.insertHtml(e.returnValues.template,s,"append"),r.show(t.get("wcf.global.success.add")),i.trigger(),window.jQuery(this._textarea).redactor("code.set",""),null!==this._options.callbackInsert&&this._options.callbackInsert(),elData(s,"responses",s.children.length),s.lastElementChild},_ajaxSetup:function(){var e=s._super.prototype._ajaxSetup.call(this);return e.data.actionName="addResponse",e}}),s}),define("WoltLabSuite/Core/Ui/Comment/Response/Edit",["Ajax","Core","Dictionary","Environment","EventHandler","Language","List","Dom/ChangeListener","Dom/Traverse","Dom/Util","Ui/Notification","Ui/ReusableDropdown","WoltLabSuite/Core/Ui/Scroll","WoltLabSuite/Core/Ui/Comment/Edit"],function(e,t,i,n,a,r,o,s,l,c,d,u,h,p){"use strict";function f(e){this.init(e)}return t.inherit(f,p,{init:function(e){this._activeElement=null,this._callbackClick=null,this._container=e,this._editorContainer=null,this._responses=new o,this.rebuild(),s.add("Ui/Comment/Response/Edit_"+c.identify(this._container),this.rebuild.bind(this))},rebuild:function(){elBySelAll(".commentResponse",this._container,function(e){if(!this._responses.has(e)){if(elDataBool(e,"can-edit")){var t=elBySel(".jsCommentResponseEditButton",e);null!==t&&(null===this._callbackClick&&(this._callbackClick=this._click.bind(this)),t.addEventListener(WCF_CLICK_EVENT,this._callbackClick))}this._responses.add(e)}}.bind(this))},_click:function(t){t.preventDefault(),null===this._activeElement?(this._activeElement=t.currentTarget.closest(".commentResponse"),this._prepare(),e.api(this,{actionName:"beginEdit",objectIDs:[this._getObjectId(this._activeElement)]})):d.show("wcf.message.error.editorAlreadyInUse",null,"warning")},_prepare:function(){this._editorContainer=elCreate("div"),this._editorContainer.className="commentEditorContainer",this._editorContainer.innerHTML='<span class="icon icon48 fa-spinner"></span>';var e=elBySel(".commentResponseContent",this._activeElement);e.insertBefore(this._editorContainer,e.firstChild)},_showMessage:function(e){c.setInnerHtml(elBySel(".commentResponseContent .userMessage",this._editorContainer.parentNode),e.returnValues.message),this._restoreMessage(),d.show()},_getEditorId:function(){return"commentResponseEditor"+this._getObjectId(this._activeElement)},_ajaxSetup:function(){return{data:{className:"wcf\\data\\comment\\response\\CommentResponseAction",parameters:{data:{objectTypeID:~~elData(this._container,"object-type-id")}}},silent:!0}}}),f}),define("WoltLabSuite/Core/Ui/Page/Header/Fixed",["Core","EventHandler","Ui/Alignment","Ui/CloseOverlay","Ui/SimpleDropdown","Ui/Screen"],function(e,t,i,n,a,r){"use strict";var o,s,l,c,d,u,h,p=!1;return{init:function(){o=elById("pageHeader"),s=elById("pageHeaderContainer"),this._initSearchBar(),r.on("screen-md-down",{match:function(){p=!0},unmatch:function(){p=!1},setup:function(){p=!0}}),t.add("com.woltlab.wcf.Search","close",this._closeSearchBar.bind(this))},_initSearchBar:function(){c=elById("pageHeaderSearch"),c.addEventListener(WCF_CLICK_EVENT,function(e){e.stopPropagation()}),l=elById("pageHeaderPanel"),d=elById("pageHeaderSearchInput"),u=elById("topMenu"),h=elById("userPanelSearchButton"),h.addEventListener(WCF_CLICK_EVENT,function(e){e.preventDefault(),e.stopPropagation(),o.classList.contains("searchBarOpen")?this._closeSearchBar():this._openSearchBar()}.bind(this)),n.add("WoltLabSuite/Core/Ui/Page/Header/Fixed",function(){o.classList.contains("searchBarForceOpen")||this._closeSearchBar()}.bind(this)),t.add("com.woltlab.wcf.MainMenuMobile","more",function(t){"com.woltlab.wcf.search"===t.identifier&&(t.handler.close(!0),e.triggerEvent(h,WCF_CLICK_EVENT))}.bind(this))},_openSearchBar:function(){window.WCF.Dropdown.Interactive.Handler.closeAll(),o.classList.add("searchBarOpen"),h.parentNode.classList.add("open"),p||i.set(c,u,{horizontal:"right"}),c.style.setProperty("top",l.clientHeight+"px",""),d.focus(),window.setTimeout(function(){d.selectionStart=d.selectionEnd=d.value.length},1)},_closeSearchBar:function(){o.classList.remove("searchBarOpen"),h.parentNode.classList.remove("open"),["bottom","left","right","top"].forEach(function(e){c.style.removeProperty(e)}),d.blur();var e=elBySel(".pageHeaderSearchType",c);a.close(e.id)}}}),define("WoltLabSuite/Core/Ui/Page/Search/Input",["Core","WoltLabSuite/Core/Ui/Search/Input"],function(e,t){"use strict";function i(e,t){this.init(e,t)}return e.inherit(i,t,{init:function(t,n){if(n=e.extend({ajax:{className:"wcf\\data\\page\\PageAction"},callbackSuccess:null},n),"function"!=typeof n.callbackSuccess)throw new Error("Expected a valid callback function for 'callbackSuccess'.");i._super.prototype.init.call(this,t,n),this._pageId=0},setPageId:function(e){this._pageId=e},_getParameters:function(e){var t=i._super.prototype._getParameters.call(this,e);return t.objectIDs=[this._pageId],t},_ajaxSuccess:function(e){this._options.callbackSuccess(e)}}),i}),define("WoltLabSuite/Core/Ui/Page/Search/Handler",["Language","StringUtil","Dom/Util","Ui/Dialog","./Input"],function(e,t,i,n,a){"use strict";var r=null,o=null,s=null,l=null,c=null,d=null;return{open:function(t,i,a,o){r=a,n.open(this),n.setTitle(this,i),s.textContent=o?e.get(o):e.get("wcf.page.pageObjectID.search.terms"),this._getSearchInputHandler().setPageId(t)},_buildList:function(i){if(this._resetList(),!Array.isArray(i.returnValues)||0===i.returnValues.length)return void elInnerError(o,e.get("wcf.page.pageObjectID.search.noResults"));for(var n,a,r,s=0,l=i.returnValues.length;s<l;s++)a=i.returnValues[s],n=a.image,/^fa-/.test(n)&&(n='<span class="icon icon48 '+n+' pointer jsTooltip" title="'+e.get("wcf.global.select")+'"></span>'),r=elCreate("li"),elData(r,"object-id",a.objectID),r.innerHTML='<div class="box48">'+n+'<div><div class="containerHeadline"><h3><a href="'+t.escapeHTML(a.link)+'">'+t.escapeHTML(a.title)+"</a></h3>"+(a.description?"<p>"+a.description+"</p>":"")+"</div></div></div>",r.addEventListener(WCF_CLICK_EVENT,this._click.bind(this)),c.appendChild(r);elShow(d)},_resetList:function(){elInnerError(o,!1),c.innerHTML="",elHide(d)},_getSearchInputHandler:function(){if(null===l){var e=this._buildList.bind(this);l=new a(elById("wcfUiPageSearchInput"),{callbackSuccess:e})}return l},_click:function(e){"A"!==e.target.nodeName&&(e.stopPropagation(),r(elData(e.currentTarget,"object-id")),n.close(this))},_dialogSetup:function(){return{id:"wcfUiPageSearchHandler",options:{onShow:function(){null===o&&(o=elById("wcfUiPageSearchInput"),s=o.parentNode.previousSibling.childNodes[0],c=elById("wcfUiPageSearchResultList"),d=elById("wcfUiPageSearchResultListContainer")),o.value="",elHide(d),c.innerHTML="",o.focus()},title:""},source:'<div class="section"><dl><dt><label for="wcfUiPageSearchInput">'+e.get("wcf.page.pageObjectID.search.terms")+'</label></dt><dd><input type="text" id="wcfUiPageSearchInput" class="long"></dd></dl></div><section id="wcfUiPageSearchResultListContainer" class="section sectionContainerList"><header class="sectionHeader"><h2 class="sectionTitle">'+e.get("wcf.page.pageObjectID.search.results")+'</h2></header><ul id="wcfUiPageSearchResultList" class="containerList wcfUiPageSearchResultList"></ul></section>'}}}}),define("WoltLabSuite/Core/Ui/Reaction/Profile/Loader",["Ajax","Core","Language"],function(e,t,i){"use strict";function n(e,t){this.init(e,t)}return n.prototype={init:function(e,t){if(this._container=elById("likeList"),this._userID=e,this._reactionTypeID=t,this._targetType="received",this._options={parameters:[]},!this._userID)throw new Error("[WoltLabSuite/Core/Ui/Reaction/Profile/Loader] Invalid parameter 'userID' given.");if(!this._reactionTypeID)throw new Error("[WoltLabSuite/Core/Ui/Reaction/Profile/Loader] Invalid parameter 'firstReactionTypeID' given.");var n=elCreate("li");n.className="likeListMore showMore",this._noMoreEntries=elCreate("small"),
+this._noMoreEntries.innerHTML=i.get("wcf.like.reaction.noMoreEntries"),this._noMoreEntries.style.display="none",n.appendChild(this._noMoreEntries),this._loadButton=elCreate("button"),this._loadButton.className="small",this._loadButton.innerHTML=i.get("wcf.like.reaction.more"),this._loadButton.addEventListener(WCF_CLICK_EVENT,this._loadReactions.bind(this)),this._loadButton.style.display="none",n.appendChild(this._loadButton),this._container.appendChild(n),2===elBySel("#likeList > li").length?this._noMoreEntries.style.display="":this._loadButton.style.display="",this._setupReactionTypeButtons(),this._setupTargetTypeButtons()},_setupReactionTypeButtons:function(){for(var e,t=elBySelAll("#reactionType .button"),i=0,n=t.length;i<n;i++)e=t[i],e.addEventListener(WCF_CLICK_EVENT,this._changeReactionTypeValue.bind(this,~~elData(e,"reaction-type-id")))},_setupTargetTypeButtons:function(){for(var e,t=elBySelAll("#likeType .button"),i=0,n=t.length;i<n;i++)e=t[i],e.addEventListener(WCF_CLICK_EVENT,this._changeTargetType.bind(this,elData(e,"like-type")))},_changeTargetType:function(e){if("given"!==e&&"received"!==e)throw new Error("[WoltLabSuite/Core/Ui/Reaction/Profile/Loader] Invalid parameter 'targetType' given.");e!==this._targetType&&(elBySel("#likeType .button.active").classList.remove("active"),elBySel('#likeType .button[data-like-type="'+e+'"]').classList.add("active"),this._targetType=e,this._reload())},_changeReactionTypeValue:function(e){this._reactionTypeID!==e&&(elBySel("#reactionType .button.active").classList.remove("active"),elBySel('#reactionType .button[data-reaction-type-id="'+e+'"]').classList.add("active"),this._reactionTypeID=e,this._reload())},_reload:function(){for(var e=elBySelAll("#likeList > li:not(:first-child):not(:last-child)"),t=0,i=e.length;t<i;t++)this._container.removeChild(e[t]);elData(this._container,"last-like-time",0),this._loadReactions()},_loadReactions:function(){this._options.parameters.userID=this._userID,this._options.parameters.lastLikeTime=elData(this._container,"last-like-time"),this._options.parameters.targetType=this._targetType,this._options.parameters.reactionTypeID=this._reactionTypeID,e.api(this,{parameters:this._options.parameters})},_ajaxSuccess:function(e){e.returnValues.template?(elBySel("#likeList > li:nth-last-child(1)").insertAdjacentHTML("beforebegin",e.returnValues.template),elData(this._container,"last-like-time",e.returnValues.lastLikeTime),this._noMoreEntries.style.display="none",this._loadButton.style.display=""):(this._noMoreEntries.style.display="",this._loadButton.style.display="none")},_ajaxSetup:function(){return{data:{actionName:"load",className:"\\wcf\\data\\reaction\\ReactionAction"}}}},n}),define("WoltLabSuite/Core/Ui/User/Activity/Recent",["Ajax","Language","Dom/Util"],function(e,t,i){"use strict";function n(e){this.init(e)}return n.prototype={init:function(e){this._containerId=e;var i=elById(this._containerId);this._list=elBySel(".recentActivityList",i);var n=elCreate("li");n.className="showMore",this._list.childElementCount?(n.innerHTML='<button class="small">'+t.get("wcf.user.recentActivity.more")+"</button>",n.children[0].addEventListener(WCF_CLICK_EVENT,this._showMore.bind(this))):n.innerHTML="<small>"+t.get("wcf.user.recentActivity.noMoreEntries")+"</small>",this._list.appendChild(n),this._showMoreItem=n,elBySelAll(".jsRecentActivitySwitchContext .button",i,function(e){e.addEventListener(WCF_CLICK_EVENT,function(t){t.preventDefault(),e.classList.contains("active")||this._switchContext()}.bind(this))}.bind(this))},_showMore:function(t){t.preventDefault(),this._showMoreItem.children[0].disabled=!0,e.api(this,{actionName:"load",parameters:{boxID:~~elData(this._list,"box-id"),filteredByFollowedUsers:elDataBool(this._list,"filtered-by-followed-users"),lastEventId:elData(this._list,"last-event-id"),lastEventTime:elData(this._list,"last-event-time"),userID:~~elData(this._list,"user-id")}})},_switchContext:function(){e.api(this,{actionName:"switchContext"},function(){window.location.hash="#"+this._containerId,window.location.reload()}.bind(this))},_ajaxSuccess:function(e){e.returnValues.template?(i.insertHtml(e.returnValues.template,this._showMoreItem,"before"),elData(this._list,"last-event-time",e.returnValues.lastEventTime),elData(this._list,"last-event-id",e.returnValues.lastEventID),this._showMoreItem.children[0].disabled=!1):this._showMoreItem.innerHTML="<small>"+t.get("wcf.user.recentActivity.noMoreEntries")+"</small>"},_ajaxSetup:function(){return{data:{className:"wcf\\data\\user\\activity\\event\\UserActivityEventAction"}}}},n}),define("WoltLabSuite/Core/Ui/User/CoverPhoto/Delete",["Ajax","EventHandler","Language","Ui/Confirmation","Ui/Notification"],function(e,t,i,n,a){"use strict";var r,o=0;return{init:function(e){r=elBySel(".jsButtonDeleteCoverPhoto"),r.addEventListener(WCF_CLICK_EVENT,this._click.bind(this)),o=e,t.add("com.woltlab.wcf.user","coverPhoto",function(e){"string"==typeof e.url&&e.url.length>0&&elShow(r.parentNode)})},_click:function(t){t.preventDefault(),n.show({confirm:e.api.bind(e,this),message:i.get("wcf.user.coverPhoto.delete.confirmMessage")})},_ajaxSuccess:function(e){elBySel(".userProfileCoverPhoto").style.setProperty("background-image","url("+e.returnValues.url+")",""),elHide(r.parentNode),a.show()},_ajaxSetup:function(){return{data:{actionName:"deleteCoverPhoto",className:"wcf\\data\\user\\UserProfileAction",parameters:{userID:o}}}}}}),define("WoltLabSuite/Core/Ui/User/CoverPhoto/Upload",["Core","EventHandler","Upload","Ui/Notification","Ui/Dialog"],function(e,t,i,n,a){"use strict";function r(e){i.call(this,"coverPhotoUploadButtonContainer","coverPhotoUploadPreview",{action:"uploadCoverPhoto",className:"wcf\\data\\user\\UserProfileAction"}),this._userId=e}return e.inherit(r,i,{_getParameters:function(){return{userID:this._userId}},_success:function(e,i){elInnerError(this._button,i.returnValues.errorMessage),this._target.innerHTML="",i.returnValues.url&&(elBySel(".userProfileCoverPhoto").style.setProperty("background-image","url("+i.returnValues.url+")",""),a.close("userProfileCoverPhotoUpload"),n.show(),t.fire("com.woltlab.wcf.user","coverPhoto",{url:i.returnValues.url}))}}),r}),define("WoltLabSuite/Core/Ui/User/Trophy/List",["Ajax","Core","Dictionary","Dom/Util","Ui/Dialog","WoltLabSuite/Core/Ui/Pagination","Dom/ChangeListener","List"],function(e,t,i,n,a,r,o,s){"use strict";function l(){this.init()}return l.prototype={init:function(){this._cache=new i,this._knownElements=new s,this._options={className:"wcf\\data\\user\\trophy\\UserTrophyAction",parameters:{}},this._rebuild(),o.add("WoltLabSuite/Core/Ui/User/Trophy/List",this._rebuild.bind(this))},_rebuild:function(){elBySelAll(".userTrophyOverlayList",void 0,function(e){this._knownElements.has(e)||(e.addEventListener(WCF_CLICK_EVENT,this._open.bind(this,elData(e,"user-id"))),this._knownElements.add(e))}.bind(this))},_open:function(e,t){t.preventDefault(),this._currentPageNo=1,this._currentUser=e,this._showPage()},_showPage:function(t){if(void 0!==t&&(this._currentPageNo=t),this._cache.has(this._currentUser)){if(0!==this._cache.get(this._currentUser).get("pageCount")&&(this._currentPageNo<1||this._currentPageNo>this._cache.get(this._currentUser).get("pageCount")))throw new RangeError("pageNo must be between 1 and "+this._cache.get(this._currentUser).get("pageCount")+" ("+this._currentPageNo+" given).")}else this._cache.set(this._currentUser,new i);if(this._cache.get(this._currentUser).has(this._currentPageNo)){var n=a.open(this,this._cache.get(this._currentUser).get(this._currentPageNo));if(a.setTitle("userTrophyListOverlay",this._cache.get(this._currentUser).get("title")),this._cache.get(this._currentUser).get("pageCount")>1){var o=elBySel(".jsPagination",n.content);null!==o&&new r(o,{activePage:this._currentPageNo,maxPage:this._cache.get(this._currentUser).get("pageCount"),callbackSwitch:this._showPage.bind(this)})}}else this._options.parameters.pageNo=this._currentPageNo,this._options.parameters.userID=this._currentUser,e.api(this,{parameters:this._options.parameters})},_ajaxSuccess:function(e){void 0!==e.returnValues.pageCount&&this._cache.get(this._currentUser).set("pageCount",~~e.returnValues.pageCount),this._cache.get(this._currentUser).set(this._currentPageNo,e.returnValues.template),this._cache.get(this._currentUser).set("title",e.returnValues.title),this._showPage()},_ajaxSetup:function(){return{data:{actionName:"getGroupedUserTrophyList",className:this._options.className}}},_dialogSetup:function(){return{id:"userTrophyListOverlay",options:{title:""},source:null}}},l}),define("WoltLabSuite/Core/Form/Builder/Field/Controller/Label",["Core","Dom/Util","Language","Ui/SimpleDropdown"],function(e,t,i,n){"use strict";function a(e,t,i){this.init(e,t,i)}return a.prototype={init:function(a,r,o){this._formFieldContainer=elById(a+"Container"),this._labelChooser=elByClass("labelChooser",this._formFieldContainer)[0],this._options=e.extend({forceSelection:!1,showWithoutSelection:!1},o),this._input=elCreate("input"),this._input.type="hidden",this._input.id=a,this._input.name=a,this._input.value=~~r,this._formFieldContainer.appendChild(this._input);var s=t.identify(this._labelChooser),l=n.getDropdownMenu(s);null===l&&(n.init(elByClass("dropdownToggle",this._labelChooser)[0]),l=n.getDropdownMenu(s));var c=null;if(this._options.showWithoutSelection||!this._options.forceSelection){c=elCreate("ul"),l.appendChild(c);var d=elCreate("li");d.className="dropdownDivider",c.appendChild(d)}if(this._options.showWithoutSelection){var u=elCreate("li");elData(u,"label-id",-1),this._blockScroll(u),c.appendChild(u);var h=elCreate("span");u.appendChild(h);var p=elCreate("span");p.className="badge label",p.innerHTML=i.get("wcf.label.withoutSelection"),h.appendChild(p)}if(!this._options.forceSelection){var u=elCreate("li");elData(u,"label-id",0),this._blockScroll(u),c.appendChild(u);var h=elCreate("span");u.appendChild(h);var p=elCreate("span");p.className="badge label",p.innerHTML=i.get("wcf.label.none"),h.appendChild(p)}elBySelAll("li:not(.dropdownDivider)",l,function(e){e.addEventListener("click",this._click.bind(this)),r&&~~elData(e,"label-id")===r&&this._selectLabel(e)}.bind(this))},_blockScroll:function(e){e.addEventListener("wheel",function(e){e.preventDefault()},{passive:!1})},_click:function(e){e.preventDefault(),this._selectLabel(e.currentTarget,!1)},_selectLabel:function(e){var t=elData(e,"label-id");t||(t=0);var i=elBySel("span > span",e),n=elBySel(".dropdownToggle > span",this._labelChooser);n.className=i.className,n.textContent=i.textContent,this._input.value=t}},a}),define("WoltLabSuite/Core/Form/Builder/Field/Controller/Rating",["Dictionary","Environment"],function(e,t){"use strict";function i(e,t,i,n){this.init(e,t,i,n)}return i.prototype={init:function(t,i,n,a){if(this._field=elBySel("#"+t+"Container"),null===this._field)throw new Error("Unknown field with id '"+t+"'");this._input=elCreate("input"),this._input.id=t,this._input.name=t,this._input.type="hidden",this._input.value=i,this._field.appendChild(this._input),this._activeCssClasses=n,this._defaultCssClasses=a,this._ratingElements=new e;var r=elBySel(".ratingList",this._field);r.addEventListener("mouseleave",this._restoreRating.bind(this)),elBySelAll("li",r,function(e){e.classList.contains("ratingMetaButton")?(e.addEventListener("click",this._metaButtonClick.bind(this)),e.addEventListener("mouseenter",this._restoreRating.bind(this))):(this._ratingElements.set(~~elData(e,"rating"),e),e.addEventListener("click",this._listItemClick.bind(this)),e.addEventListener("mouseenter",this._listItemMouseEnter.bind(this)),e.addEventListener("mouseleave",this._listItemMouseLeave.bind(this)))}.bind(this))},_listItemClick:function(e){this._input.value=~~elData(e.currentTarget,"rating"),"desktop"!==t.platform()&&this._restoreRating()},_listItemMouseEnter:function(e){var t=elData(e.currentTarget,"rating");this._ratingElements.forEach(function(e,i){var n=elByClass("icon",e)[0];this._toggleIcon(n,~~i<=~~t)}.bind(this))},_listItemMouseLeave:function(){this._ratingElements.forEach(function(e){var t=elByClass("icon",e)[0];this._toggleIcon(t,!1)}.bind(this))},_metaButtonClick:function(e){"removeRating"===elData(e.currentTarget,"action")&&(this._input.value="",this._listItemMouseLeave())},_restoreRating:function(){this._ratingElements.forEach(function(e,t){var i=elByClass("icon",e)[0];this._toggleIcon(i,~~t<=~~this._input.value)}.bind(this))},_toggleIcon:function(e,t){if(t=t||!1){for(var i=0;i<this._defaultCssClasses.length;i++)e.classList.remove(this._defaultCssClasses[i]);for(var i=0;i<this._activeCssClasses.length;i++)e.classList.add(this._activeCssClasses[i])}else{for(var i=0;i<this._activeCssClasses.length;i++)e.classList.remove(this._activeCssClasses[i]);for(var i=0;i<this._defaultCssClasses.length;i++)e.classList.add(this._defaultCssClasses[i])}}},i}),define("WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract",["./Manager"],function(e){"use strict";function t(e,t){this.init(e,t)}return t.prototype={checkDependency:function(){throw new Error("Missing implementation of WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract.checkDependency!")},getDependentNode:function(){return this._dependentElement},getField:function(){return this._field},getFields:function(){return this._fields},init:function(t,i){if(this._dependentElement=elById(t),null===this._dependentElement)throw new Error("Unknown dependent element with container id '"+t+"Container'.");if(this._field=elById(i),null===this._field){if(this._fields=[],elBySelAll("input[type=radio][name="+i+"]",void 0,function(e){this._fields.push(e)}.bind(this)),!this._fields.length)throw new Error("Unknown field with id '"+i+"'.")}else if(this._fields=[this._field],"INPUT"===this._field.tagName&&"radio"===this._field.type&&""!==elData(this._field,"no-input-id")){if(this._noField=elById(elData(this._field,"no-input-id")),null===this._noField)throw new Error("Cannot find 'no' input field for input field '"+i+"'");this._fields.push(this._noField)}e.addDependency(this)}},t}),define("WoltLabSuite/Core/Form/Builder/Field/Dependency/NonEmpty",["./Abstract","Core"],function(e,t){"use strict";function i(e,t){this.init(e,t)}return t.inherit(i,e,{checkDependency:function(){switch(this._field.tagName){case"INPUT":switch(this._field.type){case"checkbox":return this._field.checked;case"radio":return(!this._noField||!this._noField.checked)&&this._field.checked;default:return 0!==this._field.value.trim().length}case"SELECT":return 0!==this._field.value.length;case"TEXTAREA":return 0!==this._field.value.trim().length}}}),i}),define("WoltLabSuite/Core/Form/Builder/Field/Dependency/Value",["./Abstract","Core","./Manager"],function(e,t,i){"use strict";function n(e,t,i){this.init(e,t),this._isNegated=!1}return t.inherit(n,e,{checkDependency:function(){if(!this._values)throw new Error("Values have not been set.");var e;if(this._field){if(i.isHiddenByDependencies(this._field))return!1;e=this._field.value}else for(var t,n=0,a=this._fields.length;n<a;n++)if(t=this._fields[n],t.checked){if(i.isHiddenByDependencies(t))return!1;e=t.value;break}for(var n=0,a=this._values.length;n<a;n++)if(this._values[n]==e)return!this._isNegated;return!!this._isNegated},negate:function(e){return this._isNegated=e,this},values:function(e){return this._values=e,this}}),n}),define("WoltLabSuite/Core/Form/Builder/Field/Language/ContentLanguage",["Core","WoltLabSuite/Core/Language/Chooser","../Value"],function(e,t,i){"use strict";function n(e){this.init(e)}return e.inherit(n,i,{destroy:function(){t.removeChooser(this._fieldId)}}),n}),define("WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/Abstract",["EventHandler","../Manager"],function(e,t){"use strict";function i(e){this.init(e)}return i.prototype={checkContainer:function(){throw new Error("Missing implementation of WoltLabSuite/Core/Form/Builder/Field/Dependency/Container.checkContainer!")},init:function(e){if("string"!=typeof e)throw new TypeError("Container id has to be a string.");if(this._container=elById(e),null===this._container)throw new Error("Unknown container with id '"+e+"'.");t.addContainerCheckCallback(this.checkContainer.bind(this))}},i}),define("WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/Default",["./Abstract","Core","../Manager"],function(e,t,i){"use strict";function n(e){this.init(e)}return t.inherit(n,e,{checkContainer:function(){if(!elDataBool(this._container,"ignore-dependencies")&&!i.isHiddenByDependencies(this._container)){var e=!elIsHidden(this._container),t=!1,n=this._container.children,a=0;if("H2"===this._container.children.item(0).tagName||"HEADER"===this._container.children.item(0).tagName)var a=1;for(var r=a,o=n.length;r<o;r++)if(!elIsHidden(n.item(r))){t=!0;break}e!==t&&(t?elShow(this._container):elHide(this._container),i.checkContainers())}}}),n}),define("WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/Tab",["./Abstract","Core","Dom/Util","../Manager","Ui/TabMenu"],function(e,t,i,n,a){"use strict";function r(e){this.init(e)}return t.inherit(r,e,{checkContainer:function(){if(!n.isHiddenByDependencies(this._container)){for(var e=!elIsHidden(this._container),t=!1,r=this._container.children,o=0,s=r.length;o<s;o++)if(!elIsHidden(r.item(o))){t=!0;break}if(e!==t){var l=elBySel("#"+i.identify(this._container.parentNode)+" > nav > ul > li[data-name="+this._container.id+"]",this._container.parentNode.parentNode);if(null===l)throw new Error("Cannot find tab menu entry for tab '"+this._container.id+"'.");if(t)elShow(this._container),elShow(l);else{elHide(this._container),elHide(l);var c=a.getTabMenu(i.identify(l.closest(".tabMenuContainer")));c.getActiveTab()===l&&c.selectFirstVisible()}n.checkContainers()}}}}),r}),define("WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/TabMenu",["./Abstract","Core","Dom/Util","../Manager","Ui/TabMenu"],function(e,t,i,n,a){"use strict";function r(e){this.init(e)}return t.inherit(r,e,{checkContainer:function(){if(!n.isHiddenByDependencies(this._container)){for(var e=!elIsHidden(this._container),t=!1,r=elBySelAll("#"+i.identify(this._container)+" > nav > ul > li",this._container.parentNode),o=0,s=r.length;o<s;o++)if(!elIsHidden(r[o])){t=!0;break}e!==t&&(t?(elShow(this._container),a.getTabMenu(i.identify(this._container)).selectFirstVisible()):elHide(this._container),n.checkContainers())}}}),r}),define("WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList",["Dom/ChangeListener","Dom/Traverse","Dom/Util","EventKey","Language"],function(e,t,i,n,a){"use strict";function r(e,t){this.init(e,t)}return r.prototype={init:function(e,t){if(this._formFieldId=e,this._packageList=elById(this._formFieldId+"_packageList"),null===this._packageList)throw new Error("Cannot find package list for packages field with id '"+this._formFieldId+"'.");if(this._packageIdentifier=elById(this._formFieldId+"_packageIdentifier"),null===this._packageIdentifier)throw new Error("Cannot find package identifier form field for packages field with id '"+this._formFieldId+"'.");if(this._packageIdentifier.addEventListener("keypress",this._keyPress.bind(this)),this._addButton=elById(this._formFieldId+"_addButton"),null===this._addButton)throw new Error("Cannot find add button for packages field with id '"+this._formFieldId+"'.");if(this._addButton.addEventListener("click",this._addPackage.bind(this)),this._form=this._packageList.closest("form"),null===this._form)throw new Error("Cannot find form element for packages field with id '"+this._formFieldId+"'.");this._form.addEventListener("submit",this._submit.bind(this)),t.forEach(this._addPackageByData.bind(this))},_addPackage:function(e){e.preventDefault(),e.stopPropagation(),this._validateInput()&&(this._addPackageByData(this._getInputData()),this._emptyInput(),this._packageIdentifier.focus())},_addPackageByData:function(t){var n=elCreate("li");this._populateListItem(n,t);var r=elCreate("span");r.className="icon icon16 fa-times pointer jsTooltip",elAttr(r,"title",a.get("wcf.global.button.delete")),r.addEventListener("click",this._removePackage.bind(this)),i.prepend(r,n),this._packageList.appendChild(n),e.trigger()},_createSubmitFields:function(e,t){var i=elCreate("input");elAttr(i,"type","hidden"),elAttr(i,"name",this._formFieldId+"["+t+"][packageIdentifier]"),i.value=elData(e,"package-identifier"),this._form.appendChild(i)},_emptyInput:function(){this._packageIdentifier.value=""},_getErrorElement:function(e,n){var a=t.nextByClass(e,"innerError");return null===a&&n&&(a=elCreate("small"),a.className="innerError",i.insertAfter(a,e)),a},_getInputData:function(){return{packageIdentifier:this._packageIdentifier.value}},_getPackageIdentifierErrorElement:function(e){return this._getErrorElement(this._packageIdentifier,e)},_keyPress:function(e){n.Enter(e)&&this._addPackage(e)},_populateListItem:function(e,t){elData(e,"package-identifier",t.packageIdentifier)},_removePackage:function(e){elRemove(e.currentTarget.closest("li")),!this._packageList.childElementCount&&"SMALL"===this._packageList.nextElementSibling.tagName&&this._packageList.nextElementSibling.classList.contains("innerError")&&elRemove(this._packageList.nextElementSibling)},_submit:function(){t.childrenByTag(this._packageList,"LI").forEach(this._createSubmitFields.bind(this))},_validateInput:function(){return this._validatePackageIdentifier()},_validatePackageIdentifier:function(){var e=this._packageIdentifier.value;if(""===e)return this._getPackageIdentifierErrorElement(!0).textContent=a.get("wcf.global.form.error.empty"),!1;if(e.length<3)return this._getPackageIdentifierErrorElement(!0).textContent=a.get("wcf.acp.devtools.project.packageIdentifier.error.minimumLength"),!1;if(e.length>191)return this._getPackageIdentifierErrorElement(!0).textContent=a.get("wcf.acp.devtools.project.packageIdentifier.error.maximumLength"),!1;if(!e.match(/^[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/))return this._getPackageIdentifierErrorElement(!0).textContent=a.get("wcf.acp.devtools.project.packageIdentifier.error.format"),!1;var i=!1;if(t.childrenByTag(this._packageList,"LI").forEach(function(t,n){elData(t,"package-identifier")===e&&(i=!0)}),i)return this._getPackageIdentifierErrorElement(!0).textContent=a.get("wcf.acp.devtools.project.packageIdentifier.error.duplicate"),!1;var n=this._getPackageIdentifierErrorElement();return null!==n&&elRemove(n),!0},_validateVersion:function(e,t){if(""!==e){if(e.length>255)return t(!0).textContent=a.get("wcf.acp.devtools.project.packageVersion.error.maximumLength"),!1;if(!e.match(/^([0-9]+)\.([0-9]+)\.([0-9]+)(\ (a|alpha|b|beta|d|dev|rc|pl)\ ([0-9]+))?$/i))return t(!0).textContent=a.get("wcf.acp.devtools.project.packageVersion.error.format"),!1}var i=t();return null!==i&&elRemove(i),!0}},r}),define("WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/ExcludedPackages",["./AbstractPackageList","Core","Language"],function(e,t,i){"use strict";function n(e,t){this.init(e,t)}return t.inherit(n,e,{init:function(e,t){if(n._super.prototype.init.call(this,e,t),this._version=elById(this._formFieldId+"_version"),null===this._version)throw new Error("Cannot find version form field for packages field with id '"+this._formFieldId+"'.");this._version.addEventListener("keypress",this._keyPress.bind(this))},_createSubmitFields:function(e,t){n._super.prototype._createSubmitFields.call(this,e,t);var i=elCreate("input");elAttr(i,"type","hidden"),elAttr(i,"name",this._formFieldId+"["+t+"][version]"),i.value=elData(e,"version"),this._form.appendChild(i)},_emptyInput:function(){n._super.prototype._emptyInput.call(this),this._version.value=""},_getInputData:function(){return t.extend(n._super.prototype._getInputData.call(this),{version:this._version.value})},_getVersionErrorElement:function(e){return this._getErrorElement(this._version,e)},_populateListItem:function(e,t){n._super.prototype._populateListItem.call(this,e,t),elData(e,"version",t.version),e.innerHTML=" "+i.get("wcf.acp.devtools.project.excludedPackage.excludedPackage",{packageIdentifier:t.packageIdentifier,version:t.version})},_validateInput:function(){return n._super.prototype._validateInput.call(this)&&this._validateVersion(this._version.value,this._getVersionErrorElement.bind(this))}}),n}),define("WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/Instructions",["Dom/ChangeListener","Dom/Traverse","Dom/Util","EventKey","Language","Ui/Confirmation","Ui/Dialog","WoltLabSuite/Core/Ui/Sortable/List"],function(e,t,i,n,a,r,o,s){"use strict";function l(e,t,i,n,a,r){this.init(e,t,i,n,a,r||[])}var c=["acpTemplate","file","script","template"];return l.prototype={init:function(t,i,n,a,r,o){if(this._formFieldId=t,this._instructionsTemplate=i,this._instructionsEditDialogTemplate=n,this._instructionEditDialogTemplate=a,this._instructionsCounter=0,this._pipDefaultFilenames=r,this._instructionCounter=0,this._instructionsList=elById(this._formFieldId+"_instructionsList"),null===this._instructionsList)throw new Error("Cannot find package list for packages field with id '"+this._formFieldId+"'.");if(this._instructionsType=elById(this._formFieldId+"_instructionsType"),null===this._instructionsType)throw new Error("Cannot find instruction type form field for instructions field with id '"+this._formFieldId+"'.");if(this._instructionsType.addEventListener("change",this._toggleFromVersionFormField.bind(this)),this._fromVersion=elById(this._formFieldId+"_fromVersion"),null===this._fromVersion)throw new Error("Cannot find from version form field for instructions field with id '"+this._formFieldId+"'.");if(this._fromVersion.addEventListener("keypress",this._instructionsKeyPress.bind(this)),this._addButton=elById(this._formFieldId+"_addButton"),null===this._addButton)throw new Error("Cannot find add button for instructions field with id '"+this._formFieldId+"'.");if(this._addButton.addEventListener("click",this._addInstructions.bind(this)),this._form=this._instructionsList.closest("form"),null===this._form)throw new Error("Cannot find form element for instructions field with id '"+this._formFieldId+"'.");this._form.addEventListener("submit",this._submit.bind(this));var s=!1;for(var l in o){if("install"===o[l].type){s=!0;break}}s||this._addInstructionsByData({fromVersion:"",type:"install"}),o.forEach(this._addInstructionsByData.bind(this)),e.trigger()},_addInstruction:function(t){t.preventDefault(),t.stopPropagation();var i=elData(t.currentTarget.closest("li.section"),"instructions-id"),n=elById(this._formFieldId+"_instructions"+i+"_pip");if(n.value){var r=elById(this._formFieldId+"_instructions"+i+"_value"),o=elById(this._formFieldId+"_instructions"+i+"_runStandalone"),s=elById(this._formFieldId+"_instructions"+i+"_application");this._addInstructionByData(i,{application:-1!==c.indexOf(n.value)?s.value:"",pip:n.value,runStandalone:~~o.checked,value:r.value}),n.value="",r.value="",o.checked=!1,s.value="",elById(this._formFieldId+"_instructions"+i+"_valueDescription").innerHTML=a.get("wcf.acp.devtools.project.instruction.value.description"),this._toggleApplicationFormField(i),e.trigger()}},_addInstructionByData:function(e,t){var i=++this._instructionCounter,n=elById(this._formFieldId+"_instructions"+e+"_instructionList"),r=elCreate("li");r.className="sortableNode",r.id=this._formFieldId+"_instruction"+i,elData(r,"instruction-id",i),elData(r,"application",t.application),elData(r,"pip",t.pip),elData(r,"runStandalone",t.runStandalone),elData(r,"value",t.value);var o='<div class="sortableNodeLabel">\t<div class="jsDevtoolsProjectInstruction">\t\t'+a.get("wcf.acp.devtools.project.instruction.instruction",t);if(t.errors)for(var s in t.errors)o+='<small class="innerError">'+t.errors[s]+"</small>";o+='\t</div>\t<span class="statusDisplay sortableButtonContainer">\t\t<span class="icon icon16 fa-pencil pointer jsTooltip" id="'+this._formFieldId+"_instruction"+i+'_editButton" title="'+a.get("wcf.global.button.edit")+'"></span>\t\t<span class="icon icon16 fa-times pointer jsTooltip" id="'+this._formFieldId+"_instruction"+i+'_deleteButton" title="'+a.get("wcf.global.button.delete")+'"></span>\t</span></div>',r.innerHTML=o,n.appendChild(r),elById(this._formFieldId+"_instruction"+i+"_deleteButton").addEventListener("click",this._removeInstruction.bind(this)),elById(this._formFieldId+"_instruction"+i+"_editButton").addEventListener("click",this._editInstruction.bind(this))},_addInstructions:function(t){t.preventDefault(),t.stopPropagation(),this._validateInstructionsType()&&("update"!==this._instructionsType.value||this._validateFromVersion(this._fromVersion))&&(this._addInstructionsByData({fromVersion:"update"===this._instructionsType.value?this._fromVersion.value:"",type:this._instructionsType.value}),this._instructionsType.value="",this._fromVersion.value="",this._toggleFromVersionFormField(),e.trigger())},_addInstructionsByData:function(e){var t=++this._instructionsCounter,i=elCreate("li");i.className="section",i.innerHTML=this._instructionsTemplate.fetch({instructionsId:t,sectionTitle:a.get("wcf.acp.devtools.project.instructions.type."+e.type+".title",{fromVersion:e.fromVersion}),type:e.type}),i.id=this._formFieldId+"_instructions"+t,elData(i,"instructions-id",t),elData(i,"type",e.type),elData(i,"fromVersion",e.fromVersion),elById(this._formFieldId+"_instructions"+t+"_valueDescription"),this._instructionsList.appendChild(i);var n=elById(this._formFieldId+"_instructions"+t+"_instructionListContainer");Array.isArray(e.errors)&&e.errors.forEach(function(e){var t=elCreate("small");t.className="innerError",t.innerHTML=e,n.parentNode.insertBefore(t,n)}),new s({containerId:n.id,isSimpleSorting:!0,options:{toleranceElement:"> div"}});elById(this._formFieldId+"_instructions"+t+"_deleteButton");if("update"===e.type&&(elById(this._formFieldId+"_instructions"+t+"_deleteButton").addEventListener("click",this._removeInstructions.bind(this)),elById(this._formFieldId+"_instructions"+t+"_editButton").addEventListener("click",this._editInstructions.bind(this))),elById(this._formFieldId+"_instructions"+t+"_pip").addEventListener("change",this._changeInstructionPip.bind(this)),elById(this._formFieldId+"_instructions"+t+"_value").addEventListener("keypress",this._instructionKeyPress.bind(this)),elById(this._formFieldId+"_instructions"+t+"_addButton").addEventListener("click",this._addInstruction.bind(this)),e.instructions)for(var r in e.instructions)this._addInstructionByData(t,e.instructions[r])},_changeInstructionPip:function(e){var t=e.currentTarget.value,i=elData(e.currentTarget.closest("li.section"),"instructions-id"),n=elById(this._formFieldId+"_instructions"+i+"_valueDescription");""!==this._pipDefaultFilenames[t]?n.innerHTML=a.get("wcf.acp.devtools.project.instruction.value.description.defaultFilename",{defaultFilename:this._pipDefaultFilenames[t]}):n.innerHTML=a.get("wcf.acp.devtools.project.instruction.value.description");elById(this._formFieldId+"_instructions"+i+"_value").closest("dl").classList,elById(this._formFieldId+"_instructions"+i+"_application").closest("dl");this._toggleApplicationFormField(i)},_editInstruction:function(i){var r=i.currentTarget.closest("li"),s=elData(r,"instruction-id"),l=elData(r,"application"),d=elData(r,"pip"),u=elDataBool(r,"runStandalone"),h=elData(r,"value"),p=this._instructionEditDialogTemplate.fetch({runStandalone:u,value:h}),f="instructionEditDialog"+s;o.getDialog(f)?o.openStatic(f):o.openStatic(f,p,{onSetup:function(i){var r=elBySel("select[name=application]",i),u=elBySel("select[name=pip]",i),h=elBySel("input[name=runStandalone]",i),p=elBySel("input[name=value]",i);r.value=l,u.value=d;var m=function(){var t=elById(this._formFieldId+"_instruction"+s);elData(t,"application",-1!==c.indexOf(u.value)?r.value:""),elData(t,"pip",u.value),elData(t,"runStandalone",~~h.checked),elData(t,"value",p.value),elByClass("jsDevtoolsProjectInstruction",t)[0].innerHTML=a.get("wcf.acp.devtools.project.instruction.instruction",{application:elData(t,"application"),pip:elData(t,"pip"),runStandalone:elDataBool(t,"runStandalone"),
+value:elData(t,"value")}),e.trigger(),o.close(f)}.bind(this);p.addEventListener("keypress",function(e){n.Enter(e)&&m()}),elBySel("button[data-type=submit]",i).addEventListener("click",m);var g=function(){var e=u.value;-1!==c.indexOf(e)?elShow(r.closest("dl")):elHide(r.closest("dl"));var i=t.nextByTag(p,"SMALL");""!==this._pipDefaultFilenames[e]?i.innerHTML=a.get("wcf.acp.devtools.project.instruction.value.description.defaultFilename",{defaultFilename:this._pipDefaultFilenames[e]}):i.innerHTML=a.get("wcf.acp.devtools.project.instruction.value.description")}.bind(this);u.addEventListener("change",g),g()}.bind(this),title:a.get("wcf.acp.devtools.project.instruction.edit")})},_editInstructions:function(t){var i=t.currentTarget.closest("li"),r=elData(i,"instructions-id"),s=elData(i,"fromVersion"),l=this._instructionsEditDialogTemplate.fetch({fromVersion:s}),c="instructionsEditDialog"+r;o.getDialog(c)?o.openStatic(c):o.openStatic(c,l,{onSetup:function(t){var i=elBySel("input[name=fromVersion]",t),s=function(){if(this._validateFromVersion(i)){var t=elById(this._formFieldId+"_instructions"+r);elData(t,"fromVersion",i.value),elByClass("jsInstructionsTitle",t)[0].textContent=a.get("wcf.acp.devtools.project.instructions.type.update.title",{fromVersion:i.value}),e.trigger(),o.close(c)}}.bind(this);i.addEventListener("keypress",function(e){n.Enter(e)&&s()}),elBySel("button[data-type=submit]",t).addEventListener("click",s)}.bind(this),title:a.get("wcf.acp.devtools.project.instructions.edit")})},_getErrorElement:function(e,n){var a=t.nextByClass(e,"innerError");return null===a&&n&&(a=elCreate("small"),a.className="innerError",i.insertAfter(a,e)),a},_getFromVersionErrorElement:function(e,t){return this._getErrorElement(e,t)},_getInstructionsTypeErrorElement:function(e){return this._getErrorElement(this._instructionsType,e)},_instructionKeyPress:function(e){n.Enter(e)&&this._addInstruction(e)},_instructionsKeyPress:function(e){n.Enter(e)&&this._addInstructions(e)},_removeInstruction:function(e){var t=e.currentTarget.closest("li");r.show({confirm:function(){elRemove(t)},message:a.get("wcf.acp.devtools.project.instruction.delete.confirmMessages")})},_removeInstructions:function(e){var t=e.currentTarget.closest("li");r.show({confirm:function(){elRemove(t)},message:a.get("wcf.acp.devtools.project.instructions.delete.confirmMessages")})},_submit:function(e){t.childrenByTag(this._instructionsList,"LI").forEach(function(e,i){var n=this._formFieldId+"["+i+"]",a=elCreate("input");if(elAttr(a,"type","hidden"),elAttr(a,"name",n+"[type]"),a.value=elData(e,"type"),this._form.appendChild(a),"update"===a.value){var r=elCreate("input");elAttr(r,"type","hidden"),elAttr(r,"name",this._formFieldId+"["+i+"][fromVersion]"),r.value=elData(e,"fromVersion"),this._form.appendChild(r)}t.childrenByTag(elById(e.id+"_instructionList"),"LI").forEach(function(e,t){var n=this._formFieldId+"["+i+"][instructions]["+t+"]";if(["pip","value","runStandalone"].forEach(function(t){var i=elCreate("input");elAttr(i,"type","hidden"),elAttr(i,"name",n+"["+t+"]"),i.value=elData(e,t),this._form.appendChild(i)}.bind(this)),-1!==c.indexOf(elData(e,"pip"))){var a=elCreate("input");elAttr(a,"type","hidden"),elAttr(a,"name",n+"[application]"),a.value=elData(e,"application"),this._form.appendChild(a)}}.bind(this))}.bind(this))},_toggleApplicationFormField:function(e){var t=elById(this._formFieldId+"_instructions"+e+"_pip").value,i=elById(this._formFieldId+"_instructions"+e+"_value").closest("dl").classList,n=elById(this._formFieldId+"_instructions"+e+"_application").closest("dl");-1!==c.indexOf(t)?(i.remove("col-md-9"),i.add("col-md-7"),elShow(n)):(i.remove("col-md-7"),i.add("col-md-9"),elHide(n))},_toggleFromVersionFormField:function(){var e=this._instructionsType.closest("dl").classList,t=this._fromVersion.closest("dl");"update"===this._instructionsType.value?(e.remove("col-md-10"),e.add("col-md-5"),elShow(t)):(e.remove("col-md-5"),e.add("col-md-10"),elHide(t))},_validateFromVersion:function(e){var t=e.value;if(""===t)return this._getFromVersionErrorElement(e,!0).textContent=a.get("wcf.global.form.error.empty"),!1;if(t.length>50)return this._getFromVersionErrorElement(e,!0).textContent=a.get("wcf.acp.devtools.project.packageVersion.error.maximumLength"),!1;if(-1===t.indexOf("*")){if(!t.match(/^([0-9]+)\.([0-9]+)\.([0-9]+)(\ (a|alpha|b|beta|d|dev|rc|pl)\ ([0-9]+))?$/i))return this._getFromVersionErrorElement(e,!0).textContent=a.get("wcf.acp.devtools.project.packageVersion.error.format"),!1}else if(!t.replace("*","0").match(/^([0-9]+)\.([0-9]+)\.([0-9]+)(\ (a|alpha|b|beta|d|dev|rc|pl)\ ([0-9]+))?$/i))return this._getFromVersionErrorElement(e,!0).textContent=a.get("wcf.acp.devtools.project.packageVersion.error.format"),!1;var i=this._getFromVersionErrorElement(e);return null!==i&&elRemove(i),!0},_validateInstructionsType:function(){if("install"!==this._instructionsType.value&&"update"!==this._instructionsType.value)return""===this._instructionsType.value?this._getInstructionsTypeErrorElement(!0).textContent=a.get("wcf.global.form.error.empty"):this._getInstructionsTypeErrorElement(!0).textContent=a.get("wcf.global.form.error.noValidSelection"),!1;if("install"===this._instructionsType.value){var e=!1;if([].forEach.call(this._instructionsList.children,function(t){"install"===elData(t,"type")&&(e=!0)}),e)return this._getInstructionsTypeErrorElement(!0).textContent=a.get("wcf.acp.devtools.project.instructions.type.update.error.duplicate"),!1}var t=this._getInstructionsTypeErrorElement();return null!==t&&elRemove(t),!0}},l}),define("WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/OptionalPackages",["./AbstractPackageList","Core","Language"],function(e,t,i){"use strict";function n(e,t){this.init(e,t)}return t.inherit(n,e,{_populateListItem:function(e,t){n._super.prototype._populateListItem.call(this,e,t),e.innerHTML=" "+i.get("wcf.acp.devtools.project.optionalPackage.optionalPackage",{file:t.file,packageIdentifier:t.packageIdentifier})}}),n}),define("WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/RequiredPackages",["./AbstractPackageList","Core","Language"],function(e,t,i){"use strict";function n(e,t){this.init(e,t)}return t.inherit(n,e,{init:function(e,t){if(n._super.prototype.init.call(this,e,t),this._minVersion=elById(this._formFieldId+"_minVersion"),null===this._minVersion)throw new Error("Cannot find minimum version form field for packages field with id '"+this._formFieldId+"'.");if(this._minVersion.addEventListener("keypress",this._keyPress.bind(this)),this._file=elById(this._formFieldId+"_file"),null===this._file)throw new Error("Cannot find file form field for required field with id '"+this._formFieldId+"'.")},_createSubmitFields:function(e,t){n._super.prototype._createSubmitFields.call(this,e,t);var i=elCreate("input");elAttr(i,"type","hidden"),elAttr(i,"name",this._formFieldId+"["+t+"][minVersion]"),i.value=elData(e,"min-version"),this._form.appendChild(i);var a=elCreate("input");elAttr(a,"type","hidden"),elAttr(a,"name",this._formFieldId+"["+t+"][file]"),a.value=elData(e,"file"),this._form.appendChild(a)},_emptyInput:function(){n._super.prototype._emptyInput.call(this),this._minVersion.value="",this._file.checked=!1},_getInputData:function(){return t.extend(n._super.prototype._getInputData.call(this),{file:this._file.checked,minVersion:this._minVersion.value})},_getMinVersionErrorElement:function(e){return this._getErrorElement(this._minVersion,e)},_populateListItem:function(e,t){n._super.prototype._populateListItem.call(this,e,t),elData(e,"min-version",t.minVersion),elData(e,"file",~~t.file),e.innerHTML=" "+i.get("wcf.acp.devtools.project.requiredPackage.requiredPackage",{file:~~t.file,minVersion:t.minVersion,packageIdentifier:t.packageIdentifier})},_validateInput:function(){return n._super.prototype._validateInput.call(this)&&this._validateVersion(this._minVersion.value,this._getMinVersionErrorElement.bind(this))}}),n}),define("WoltLabSuite/Core/Ui/User/Profile/Menu/Item/Abstract",["Ajax","Dom/Util"],function(e,t){"use strict";function i(e,t){}return i.prototype={init:function(e,t){this._userId=e,this._isActive=!1!==t,this._initButton(),this._updateButton()},_initButton:function(){var e=elCreate("a");e.href="#",e.addEventListener(WCF_CLICK_EVENT,this._toggle.bind(this));var i=elCreate("li");i.appendChild(e);var n=elBySel('.userProfileButtonMenu[data-menu="interaction"]');t.prepend(i,n),this._button=e,this._listItem=i},_toggle:function(t){t.preventDefault(),e.api(this,{actionName:this._getAjaxActionName(),parameters:{data:{userID:this._userId}}})},_updateButton:function(){this._button.textContent=this._getLabel(),this._listItem.classList[this._isActive?"add":"remove"]("active")},_getLabel:function(){throw new Error("Implement me!")},_getAjaxActionName:function(){throw new Error("Implement me!")},_ajaxSuccess:function(){throw new Error("Implement me!")},_ajaxSetup:function(){throw new Error("Implement me!")}},i}),define("WoltLabSuite/Core/Ui/User/Profile/Menu/Item/Follow",["Core","Language","Ui/Notification","./Abstract"],function(e,t,i,n){"use strict";function a(e,t){this.init(e,t)}return e.inherit(a,n,{_getLabel:function(){return t.get("wcf.user.button."+(this._isActive?"un":"")+"follow")},_getAjaxActionName:function(){return this._isActive?"unfollow":"follow"},_ajaxSuccess:function(e){this._isActive=!!e.returnValues.following,this._updateButton(),i.show()},_ajaxSetup:function(){return{data:{className:"wcf\\data\\user\\follow\\UserFollowAction"}}}}),a}),define("WoltLabSuite/Core/Ui/User/Profile/Menu/Item/Ignore",["Core","Language","Ui/Notification","./Abstract"],function(e,t,i,n){"use strict";function a(e,t){this.init(e,t)}return e.inherit(a,n,{_getLabel:function(){return t.get("wcf.user.button."+(this._isActive?"un":"")+"ignore")},_getAjaxActionName:function(){return this._isActive?"unignore":"ignore"},_ajaxSuccess:function(e){this._isActive=!!e.returnValues.isIgnoredUser,this._updateButton(),i.show()},_ajaxSetup:function(){return{data:{className:"wcf\\data\\user\\ignore\\UserIgnoreAction"}}}}),a}),function(e){e.matches=e.matches||e.mozMatchesSelector||e.msMatchesSelector||e.oMatchesSelector||e.webkitMatchesSelector,e.closest=e.closest||function(e){for(var t=this;t&&!t.matches(e);)t=t.parentElement;return t}}(Element.prototype),define("closest",function(){}),function(e){function t(){for(;n.length&&"function"==typeof n[0];)n.shift()()}var i=e.require,n=[],a=0;e.orgRequire=i,e.require=function(r,o,s){if(!Array.isArray(r))return i.apply(e,arguments);var l=new Promise(function(e,o){var s=a++;n.push(s),i(r,function(){var i=arguments;n[n.indexOf(s)]=function(){e(i)},t()},function(e){n[n.indexOf(s)]=function(){o(e)},t()})});return o&&(l=l.then(function(t){return o.apply(e,t)})),s&&l.catch(s),l},e.require.config=i.config}(window),define("require.linearExecution",function(){});
\ No newline at end of file
index 112109e60a0363484ac48dd997eb64ec03c988be..dba4687b18b682781949664951fe30e4e5ac55a1 100644 (file)
 
 
 // WoltLabSuite.Core.tiny.min.js
-/**
- * @license alameda 1.2.0 Copyright jQuery Foundation and other contributors.
- * Released under MIT license, https://github.com/requirejs/alameda/blob/master/LICENSE
- */
-// Going sloppy because loader plugin execs may depend on non-strict execution.
-/*jslint sloppy: true, nomen: true, regexp: true */
-/*global document, navigator, importScripts, Promise, setTimeout */
-
-var requirejs, require, define;
-(function (global, Promise, undef) {
-  if (!Promise) {
-    throw new Error('No Promise implementation available');
-  }
-
-  var topReq, dataMain, src, subPath,
-    bootstrapConfig = requirejs || require,
-    hasOwn = Object.prototype.hasOwnProperty,
-    contexts = {},
-    queue = [],
-    currDirRegExp = /^\.\//,
-    urlRegExp = /^\/|\:|\?|\.js$/,
-    commentRegExp = /\/\*[\s\S]*?\*\/|([^:"'=]|^)\/\/.*$/mg,
-    cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,
-    jsSuffixRegExp = /\.js$/,
-    slice = Array.prototype.slice;
-
-  if (typeof requirejs === 'function') {
-    return;
-  }
-
-  var asap = Promise.resolve(undefined);
-
-  // Could match something like ')//comment', do not lose the prefix to comment.
-  function commentReplace(match, singlePrefix) {
-    return singlePrefix || '';
-  }
-
-  function hasProp(obj, prop) {
-    return hasOwn.call(obj, prop);
-  }
-
-  function getOwn(obj, prop) {
-    return obj && hasProp(obj, prop) && obj[prop];
-  }
-
-  function obj() {
-    return Object.create(null);
-  }
-
-  /**
-   * Cycles over properties in an object and calls a function for each
-   * property value. If the function returns a truthy value, then the
-   * iteration is stopped.
-   */
-  function eachProp(obj, func) {
-    var prop;
-    for (prop in obj) {
-      if (hasProp(obj, prop)) {
-        if (func(obj[prop], prop)) {
-          break;
-        }
-      }
-    }
-  }
-
-  /**
-   * Simple function to mix in properties from source into target,
-   * but only if target does not already have a property of the same name.
-   */
-  function mixin(target, source, force, deepStringMixin) {
-    if (source) {
-      eachProp(source, function (value, prop) {
-        if (force || !hasProp(target, prop)) {
-          if (deepStringMixin && typeof value === 'object' && value &&
-            !Array.isArray(value) && typeof value !== 'function' &&
-            !(value instanceof RegExp)) {
-
-            if (!target[prop]) {
-              target[prop] = {};
-            }
-            mixin(target[prop], value, force, deepStringMixin);
-          } else {
-            target[prop] = value;
-          }
-        }
-      });
-    }
-    return target;
-  }
-
-  // Allow getting a global that expressed in
-  // dot notation, like 'a.b.c'.
-  function getGlobal(value) {
-    if (!value) {
-      return value;
-    }
-    var g = global;
-    value.split('.').forEach(function (part) {
-      g = g[part];
-    });
-    return g;
-  }
-
-  function newContext(contextName) {
-    var req, main, makeMap, callDep, handlers, checkingLater, load, context,
-      defined = obj(),
-      waiting = obj(),
-      config = {
-        // Defaults. Do not set a default for map
-        // config to speed up normalize(), which
-        // will run faster if there is no default.
-        waitSeconds: 7,
-        baseUrl: './',
-        paths: {},
-        bundles: {},
-        pkgs: {},
-        shim: {},
-        config: {}
-      },
-      mapCache = obj(),
-      requireDeferreds = [],
-      deferreds = obj(),
-      calledDefine = obj(),
-      calledPlugin = obj(),
-      loadCount = 0,
-      startTime = (new Date()).getTime(),
-      errCount = 0,
-      trackedErrors = obj(),
-      urlFetched = obj(),
-      bundlesMap = obj(),
-      asyncResolve = Promise.resolve();
-
-    /**
-     * Trims the . and .. from an array of path segments.
-     * It will keep a leading path segment if a .. will become
-     * the first path segment, to help with module name lookups,
-     * which act like paths, but can be remapped. But the end result,
-     * all paths that use this function should look normalized.
-     * NOTE: this method MODIFIES the input array.
-     * @param {Array} ary the array of path segments.
-     */
-    function trimDots(ary) {
-      var i, part, length = ary.length;
-      for (i = 0; i < length; i++) {
-        part = ary[i];
-        if (part === '.') {
-          ary.splice(i, 1);
-          i -= 1;
-        } else if (part === '..') {
-          // If at the start, or previous value is still ..,
-          // keep them so that when converted to a path it may
-          // still work when converted to a path, even though
-          // as an ID it is less than ideal. In larger point
-          // releases, may be better to just kick out an error.
-          if (i === 0 || (i === 1 && ary[2] === '..') || ary[i - 1] === '..') {
-            continue;
-          } else if (i > 0) {
-            ary.splice(i - 1, 2);
-            i -= 2;
-          }
-        }
-      }
-    }
-
-    /**
-     * Given a relative module name, like ./something, normalize it to
-     * a real name that can be mapped to a path.
-     * @param {String} name the relative name
-     * @param {String} baseName a real name that the name arg is relative
-     * to.
-     * @param {Boolean} applyMap apply the map config to the value. Should
-     * only be done if this normalization is for a dependency ID.
-     * @returns {String} normalized name
-     */
-    function normalize(name, baseName, applyMap) {
-      var pkgMain, mapValue, nameParts, i, j, nameSegment, lastIndex,
-        foundMap, foundI, foundStarMap, starI,
-        baseParts = baseName && baseName.split('/'),
-        normalizedBaseParts = baseParts,
-        map = config.map,
-        starMap = map && map['*'];
-
-
-      //Adjust any relative paths.
-      if (name) {
-        name = name.split('/');
-        lastIndex = name.length - 1;
-
-        // If wanting node ID compatibility, strip .js from end
-        // of IDs. Have to do this here, and not in nameToUrl
-        // because node allows either .js or non .js to map
-        // to same file.
-        if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
-          name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
-        }
-
-        // Starts with a '.' so need the baseName
-        if (name[0].charAt(0) === '.' && baseParts) {
-          //Convert baseName to array, and lop off the last part,
-          //so that . matches that 'directory' and not name of the baseName's
-          //module. For instance, baseName of 'one/two/three', maps to
-          //'one/two/three.js', but we want the directory, 'one/two' for
-          //this normalization.
-          normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
-          name = normalizedBaseParts.concat(name);
-        }
-
-        trimDots(name);
-        name = name.join('/');
-      }
-
-      // Apply map config if available.
-      if (applyMap && map && (baseParts || starMap)) {
-        nameParts = name.split('/');
-
-        outerLoop: for (i = nameParts.length; i > 0; i -= 1) {
-          nameSegment = nameParts.slice(0, i).join('/');
-
-          if (baseParts) {
-            // Find the longest baseName segment match in the config.
-            // So, do joins on the biggest to smallest lengths of baseParts.
-            for (j = baseParts.length; j > 0; j -= 1) {
-              mapValue = getOwn(map, baseParts.slice(0, j).join('/'));
-
-              // baseName segment has config, find if it has one for
-              // this name.
-              if (mapValue) {
-                mapValue = getOwn(mapValue, nameSegment);
-                if (mapValue) {
-                  // Match, update name to the new value.
-                  foundMap = mapValue;
-                  foundI = i;
-                  break outerLoop;
-                }
-              }
-            }
-          }
-
-          // Check for a star map match, but just hold on to it,
-          // if there is a shorter segment match later in a matching
-          // config, then favor over this star map.
-          if (!foundStarMap && starMap && getOwn(starMap, nameSegment)) {
-            foundStarMap = getOwn(starMap, nameSegment);
-            starI = i;
-          }
-        }
-
-        if (!foundMap && foundStarMap) {
-          foundMap = foundStarMap;
-          foundI = starI;
-        }
-
-        if (foundMap) {
-          nameParts.splice(0, foundI, foundMap);
-          name = nameParts.join('/');
-        }
-      }
-
-      // If the name points to a package's name, use
-      // the package main instead.
-      pkgMain = getOwn(config.pkgs, name);
-
-      return pkgMain ? pkgMain : name;
-    }
-
-    function makeShimExports(value) {
-      function fn() {
-        var ret;
-        if (value.init) {
-          ret = value.init.apply(global, arguments);
-        }
-        return ret || (value.exports && getGlobal(value.exports));
-      }
-      return fn;
-    }
-
-    function takeQueue(anonId) {
-      var i, id, args, shim;
-      for (i = 0; i < queue.length; i += 1) {
-        // Peek to see if anon
-        if (typeof queue[i][0] !== 'string') {
-          if (anonId) {
-            queue[i].unshift(anonId);
-            anonId = undef;
-          } else {
-            // Not our anon module, stop.
-            break;
-          }
-        }
-        args = queue.shift();
-        id = args[0];
-        i -= 1;
-
-        if (!(id in defined) && !(id in waiting)) {
-          if (id in deferreds) {
-            main.apply(undef, args);
-          } else {
-            waiting[id] = args;
-          }
-        }
-      }
-
-      // if get to the end and still have anonId, then could be
-      // a shimmed dependency.
-      if (anonId) {
-        shim = getOwn(config.shim, anonId) || {};
-        main(anonId, shim.deps || [], shim.exportsFn);
-      }
-    }
-
-    function makeRequire(relName, topLevel) {
-      var req = function (deps, callback, errback, alt) {
-        var name, cfg;
-
-        if (topLevel) {
-          takeQueue();
-        }
-
-        if (typeof deps === "string") {
-          if (handlers[deps]) {
-            return handlers[deps](relName);
-          }
-          // Just return the module wanted. In this scenario, the
-          // deps arg is the module name, and second arg (if passed)
-          // is just the relName.
-          // Normalize module name, if it contains . or ..
-          name = makeMap(deps, relName, true).id;
-          if (!(name in defined)) {
-            throw new Error('Not loaded: ' + name);
-          }
-          return defined[name];
-        } else if (deps && !Array.isArray(deps)) {
-          // deps is a config object, not an array.
-          cfg = deps;
-          deps = undef;
-
-          if (Array.isArray(callback)) {
-            // callback is an array, which means it is a dependency list.
-            // Adjust args if there are dependencies
-            deps = callback;
-            callback = errback;
-            errback = alt;
-          }
-
-          if (topLevel) {
-            // Could be a new context, so call returned require
-            return req.config(cfg)(deps, callback, errback);
-          }
-        }
-
-        // Support require(['a'])
-        callback = callback || function () {
-          // In case used later as a promise then value, return the
-          // arguments as an array.
-          return slice.call(arguments, 0);
-        };
-
-        // Complete async to maintain expected execution semantics.
-        return asyncResolve.then(function () {
-          // Grab any modules that were defined after a require call.
-          takeQueue();
-
-          return main(undef, deps || [], callback, errback, relName);
-        });
-      };
-
-      req.isBrowser = typeof document !== 'undefined' &&
-        typeof navigator !== 'undefined';
-
-      req.nameToUrl = function (moduleName, ext, skipExt) {
-        var paths, syms, i, parentModule, url,
-          parentPath, bundleId,
-          pkgMain = getOwn(config.pkgs, moduleName);
-
-        if (pkgMain) {
-          moduleName = pkgMain;
-        }
-
-        bundleId = getOwn(bundlesMap, moduleName);
-
-        if (bundleId) {
-          return req.nameToUrl(bundleId, ext, skipExt);
-        }
-
-        // If a colon is in the URL, it indicates a protocol is used and it is
-        // just an URL to a file, or if it starts with a slash, contains a query
-        // arg (i.e. ?) or ends with .js, then assume the user meant to use an
-        // url and not a module id. The slash is important for protocol-less
-        // URLs as well as full paths.
-        if (urlRegExp.test(moduleName)) {
-          // Just a plain path, not module name lookup, so just return it.
-          // Add extension if it is included. This is a bit wonky, only non-.js
-          // things pass an extension, this method probably needs to be
-          // reworked.
-          url = moduleName + (ext || '');
-        } else {
-          // A module that needs to be converted to a path.
-          paths = config.paths;
-
-          syms = moduleName.split('/');
-          // For each module name segment, see if there is a path
-          // registered for it. Start with most specific name
-          // and work up from it.
-          for (i = syms.length; i > 0; i -= 1) {
-            parentModule = syms.slice(0, i).join('/');
-
-            parentPath = getOwn(paths, parentModule);
-            if (parentPath) {
-              // If an array, it means there are a few choices,
-              // Choose the one that is desired
-              if (Array.isArray(parentPath)) {
-                parentPath = parentPath[0];
-              }
-              syms.splice(0, i, parentPath);
-              break;
-            }
-          }
-
-          // Join the path parts together, then figure out if baseUrl is needed.
-          url = syms.join('/');
-          url += (ext || (/^data\:|^blob\:|\?/.test(url) || skipExt ? '' : '.js'));
-          url = (url.charAt(0) === '/' ||
-                url.match(/^[\w\+\.\-]+:/) ? '' : config.baseUrl) + url;
-        }
-
-        return config.urlArgs && !/^blob\:/.test(url) ?
-               url + config.urlArgs(moduleName, url) : url;
-      };
-
-      /**
-       * Converts a module name + .extension into an URL path.
-       * *Requires* the use of a module name. It does not support using
-       * plain URLs like nameToUrl.
-       */
-      req.toUrl = function (moduleNamePlusExt) {
-        var ext,
-          index = moduleNamePlusExt.lastIndexOf('.'),
-          segment = moduleNamePlusExt.split('/')[0],
-          isRelative = segment === '.' || segment === '..';
-
-        // Have a file extension alias, and it is not the
-        // dots from a relative path.
-        if (index !== -1 && (!isRelative || index > 1)) {
-          ext = moduleNamePlusExt.substring(index, moduleNamePlusExt.length);
-          moduleNamePlusExt = moduleNamePlusExt.substring(0, index);
-        }
-
-        return req.nameToUrl(normalize(moduleNamePlusExt, relName), ext, true);
-      };
-
-      req.defined = function (id) {
-        return makeMap(id, relName, true).id in defined;
-      };
-
-      req.specified = function (id) {
-        id = makeMap(id, relName, true).id;
-        return id in defined || id in deferreds;
-      };
-
-      return req;
-    }
-
-    function resolve(name, d, value) {
-      if (name) {
-        defined[name] = value;
-        if (requirejs.onResourceLoad) {
-          requirejs.onResourceLoad(context, d.map, d.deps);
-        }
-      }
-      d.finished = true;
-      d.resolve(value);
-    }
-
-    function reject(d, err) {
-      d.finished = true;
-      d.rejected = true;
-      d.reject(err);
-    }
-
-    function makeNormalize(relName) {
-      return function (name) {
-        return normalize(name, relName, true);
-      };
-    }
-
-    function defineModule(d) {
-      d.factoryCalled = true;
-
-      var ret,
-        name = d.map.id;
-
-      try {
-        ret = context.execCb(name, d.factory, d.values, defined[name]);
-      } catch(err) {
-        return reject(d, err);
-      }
-
-      if (name) {
-        // Favor return value over exports. If node/cjs in play,
-        // then will not have a return value anyway. Favor
-        // module.exports assignment over exports object.
-        if (ret === undef) {
-          if (d.cjsModule) {
-            ret = d.cjsModule.exports;
-          } else if (d.usingExports) {
-            ret = defined[name];
-          }
-        }
-      } else {
-        // Remove the require deferred from the list to
-        // make cycle searching faster. Do not need to track
-        // it anymore either.
-        requireDeferreds.splice(requireDeferreds.indexOf(d), 1);
-      }
-      resolve(name, d, ret);
-    }
-
-    // This method is attached to every module deferred,
-    // so the "this" in here is the module deferred object.
-    function depFinished(val, i) {
-      if (!this.rejected && !this.depDefined[i]) {
-        this.depDefined[i] = true;
-        this.depCount += 1;
-        this.values[i] = val;
-        if (!this.depending && this.depCount === this.depMax) {
-          defineModule(this);
-        }
-      }
-    }
-
-    function makeDefer(name, calculatedMap) {
-      var d = {};
-      d.promise = new Promise(function (resolve, reject) {
-        d.resolve = resolve;
-        d.reject = function(err) {
-          if (!name) {
-          requireDeferreds.splice(requireDeferreds.indexOf(d), 1);
-          }
-          reject(err);
-        };
-      });
-      d.map = name ? (calculatedMap || makeMap(name)) : {};
-      d.depCount = 0;
-      d.depMax = 0;
-      d.values = [];
-      d.depDefined = [];
-      d.depFinished = depFinished;
-      if (d.map.pr) {
-        // Plugin resource ID, implicitly
-        // depends on plugin. Track it in deps
-        // so cycle breaking can work
-        d.deps = [makeMap(d.map.pr)];
-      }
-      return d;
-    }
-
-    function getDefer(name, calculatedMap) {
-      var d;
-      if (name) {
-        d = (name in deferreds) && deferreds[name];
-        if (!d) {
-          d = deferreds[name] = makeDefer(name, calculatedMap);
-        }
-      } else {
-        d = makeDefer();
-        requireDeferreds.push(d);
-      }
-      return d;
-    }
-
-    function makeErrback(d, name) {
-      return function (err) {
-        if (!d.rejected) {
-          if (!err.dynaId) {
-            err.dynaId = 'id' + (errCount += 1);
-            err.requireModules = [name];
-          }
-          reject(d, err);
-        }
-      };
-    }
-
-    function waitForDep(depMap, relName, d, i) {
-      d.depMax += 1;
-
-      // Do the fail at the end to catch errors
-      // in the then callback execution.
-      callDep(depMap, relName).then(function (val) {
-        d.depFinished(val, i);
-      }, makeErrback(d, depMap.id)).catch(makeErrback(d, d.map.id));
-    }
-
-    function makeLoad(id) {
-      var fromTextCalled;
-      function load(value) {
-        // Protect against older plugins that call load after
-        // calling load.fromText
-        if (!fromTextCalled) {
-          resolve(id, getDefer(id), value);
-        }
-      }
-
-      load.error = function (err) {
-        getDefer(id).reject(err);
-      };
-
-      load.fromText = function (text, textAlt) {
-        /*jslint evil: true */
-        var d = getDefer(id),
-          map = makeMap(makeMap(id).n),
-           plainId = map.id;
-
-        fromTextCalled = true;
-
-        // Set up the factory just to be a return of the value from
-        // plainId.
-        d.factory = function (p, val) {
-          return val;
-        };
-
-        // As of requirejs 2.1.0, support just passing the text, to reinforce
-        // fromText only being called once per resource. Still
-        // support old style of passing moduleName but discard
-        // that moduleName in favor of the internal ref.
-        if (textAlt) {
-          text = textAlt;
-        }
-
-        // Transfer any config to this other module.
-        if (hasProp(config.config, id)) {
-          config.config[plainId] = config.config[id];
-        }
-
-        try {
-          req.exec(text);
-        } catch (e) {
-          reject(d, new Error('fromText eval for ' + plainId +
-                  ' failed: ' + e));
-        }
-
-        // Execute any waiting define created by the plainId
-        takeQueue(plainId);
-
-        // Mark this as a dependency for the plugin
-        // resource
-        d.deps = [map];
-        waitForDep(map, null, d, d.deps.length);
-      };
-
-      return load;
-    }
-
-    load = typeof importScripts === 'function' ?
-        function (map) {
-          var url = map.url;
-          if (urlFetched[url]) {
-            return;
-          }
-          urlFetched[url] = true;
-
-          // Ask for the deferred so loading is triggered.
-          // Do this before loading, since loading is sync.
-          getDefer(map.id);
-          importScripts(url);
-          takeQueue(map.id);
-        } :
-        function (map) {
-          var script,
-            id = map.id,
-            url = map.url;
-
-          if (urlFetched[url]) {
-            return;
-          }
-          urlFetched[url] = true;
-
-          script = document.createElement('script');
-          script.setAttribute('data-requiremodule', id);
-          script.type = config.scriptType || 'text/javascript';
-          script.charset = 'utf-8';
-          script.async = true;
-
-          loadCount += 1;
-
-          script.addEventListener('load', function () {
-            loadCount -= 1;
-            takeQueue(id);
-          }, false);
-          script.addEventListener('error', function () {
-            loadCount -= 1;
-            var err,
-              pathConfig = getOwn(config.paths, id);
-            if (pathConfig && Array.isArray(pathConfig) &&
-                pathConfig.length > 1) {
-              script.parentNode.removeChild(script);
-              // Pop off the first array value, since it failed, and
-              // retry
-              pathConfig.shift();
-              var d = getDefer(id);
-              d.map = makeMap(id);
-              // mapCache will have returned previous map value, update the
-              // url, which will also update mapCache value.
-              d.map.url = req.nameToUrl(id);
-              load(d.map);
-            } else {
-              err = new Error('Load failed: ' + id + ': ' + script.src);
-              err.requireModules = [id];
-              getDefer(id).reject(err);
-            }
-          }, false);
-
-          script.src = url;
-
-          // If the script is cached, IE10 executes the script body and the
-          // onload handler synchronously here.  That's a spec violation,
-          // so be sure to do this asynchronously.
-          if (document.documentMode === 10) {
-            asap.then(function() {
-              document.head.appendChild(script);
-            });
-          } else {
-            document.head.appendChild(script);
-          }
-        };
-
-    function callPlugin(plugin, map, relName) {
-      plugin.load(map.n, makeRequire(relName), makeLoad(map.id), config);
-    }
-
-    callDep = function (map, relName) {
-      var args, bundleId,
-        name = map.id,
-        shim = config.shim[name];
-
-      if (name in waiting) {
-        args = waiting[name];
-        delete waiting[name];
-        main.apply(undef, args);
-      } else if (!(name in deferreds)) {
-        if (map.pr) {
-          // If a bundles config, then just load that file instead to
-          // resolve the plugin, as it is built into that bundle.
-          if ((bundleId = getOwn(bundlesMap, name))) {
-            map.url = req.nameToUrl(bundleId);
-            load(map);
-          } else {
-            return callDep(makeMap(map.pr)).then(function (plugin) {
-              // Redo map now that plugin is known to be loaded
-              var newMap = map.prn ? map : makeMap(name, relName, true),
-                newId = newMap.id,
-                shim = getOwn(config.shim, newId);
-
-              // Make sure to only call load once per resource. Many
-              // calls could have been queued waiting for plugin to load.
-              if (!(newId in calledPlugin)) {
-                calledPlugin[newId] = true;
-                if (shim && shim.deps) {
-                  req(shim.deps, function () {
-                    callPlugin(plugin, newMap, relName);
-                  });
-                } else {
-                  callPlugin(plugin, newMap, relName);
-                }
-              }
-              return getDefer(newId).promise;
-            });
-          }
-        } else if (shim && shim.deps) {
-          req(shim.deps, function () {
-            load(map);
-          });
-        } else {
-          load(map);
-        }
-      }
-
-      return getDefer(name).promise;
-    };
-
-    // Turns a plugin!resource to [plugin, resource]
-    // with the plugin being undefined if the name
-    // did not have a plugin prefix.
-    function splitPrefix(name) {
-      var prefix,
-        index = name ? name.indexOf('!') : -1;
-      if (index > -1) {
-        prefix = name.substring(0, index);
-        name = name.substring(index + 1, name.length);
-      }
-      return [prefix, name];
-    }
-
-    /**
-     * Makes a name map, normalizing the name, and using a plugin
-     * for normalization if necessary. Grabs a ref to plugin
-     * too, as an optimization.
-     */
-    makeMap = function (name, relName, applyMap) {
-      if (typeof name !== 'string') {
-        return name;
-      }
-
-      var plugin, url, parts, prefix, result, prefixNormalized,
-        cacheKey = name + ' & ' + (relName || '') + ' & ' + !!applyMap;
-
-      parts = splitPrefix(name);
-      prefix = parts[0];
-      name = parts[1];
-
-      if (!prefix && (cacheKey in mapCache)) {
-        return mapCache[cacheKey];
-      }
-
-      if (prefix) {
-        prefix = normalize(prefix, relName, applyMap);
-        plugin = (prefix in defined) && defined[prefix];
-      }
-
-      // Normalize according
-      if (prefix) {
-        if (plugin && plugin.normalize) {
-          name = plugin.normalize(name, makeNormalize(relName));
-          prefixNormalized = true;
-        } else {
-          // If nested plugin references, then do not try to
-          // normalize, as it will not normalize correctly. This
-          // places a restriction on resourceIds, and the longer
-          // term solution is not to normalize until plugins are
-          // loaded and all normalizations to allow for async
-          // loading of a loader plugin. But for now, fixes the
-          // common uses. Details in requirejs#1131
-          name = name.indexOf('!') === -1 ?
-                   normalize(name, relName, applyMap) :
-                   name;
-        }
-      } else {
-        name = normalize(name, relName, applyMap);
-        parts = splitPrefix(name);
-        prefix = parts[0];
-        name = parts[1];
-
-        url = req.nameToUrl(name);
-      }
-
-      // Using ridiculous property names for space reasons
-      result = {
-        id: prefix ? prefix + '!' + name : name, // fullName
-        n: name,
-        pr: prefix,
-        url: url,
-        prn: prefix && prefixNormalized
-      };
-
-      if (!prefix) {
-        mapCache[cacheKey] = result;
-      }
-
-      return result;
-    };
-
-    handlers = {
-      require: function (name) {
-        return makeRequire(name);
-      },
-      exports: function (name) {
-        var e = defined[name];
-        if (typeof e !== 'undefined') {
-          return e;
-        } else {
-          return (defined[name] = {});
-        }
-      },
-      module: function (name) {
-        return {
-          id: name,
-          uri: '',
-          exports: handlers.exports(name),
-          config: function () {
-            return getOwn(config.config, name) || {};
-          }
-        };
-      }
-    };
-
-    function breakCycle(d, traced, processed) {
-      var id = d.map.id;
-
-      traced[id] = true;
-      if (!d.finished && d.deps) {
-        d.deps.forEach(function (depMap) {
-          var depId = depMap.id,
-            dep = !hasProp(handlers, depId) && getDefer(depId, depMap);
-
-          // Only force things that have not completed
-          // being defined, so still in the registry,
-          // and only if it has not been matched up
-          // in the module already.
-          if (dep && !dep.finished && !processed[depId]) {
-            if (hasProp(traced, depId)) {
-              d.deps.forEach(function (depMap, i) {
-                if (depMap.id === depId) {
-                  d.depFinished(defined[depId], i);
-                }
-              });
-            } else {
-              breakCycle(dep, traced, processed);
-            }
-          }
-        });
-      }
-      processed[id] = true;
-    }
-
-    function check(d) {
-      var err, mid, dfd,
-        notFinished = [],
-        waitInterval = config.waitSeconds * 1000,
-        // It is possible to disable the wait interval by using waitSeconds 0.
-        expired = waitInterval &&
-                  (startTime + waitInterval) < (new Date()).getTime();
-
-    if (loadCount === 0) {
-        // If passed in a deferred, it is for a specific require call.
-        // Could be a sync case that needs resolution right away.
-        // Otherwise, if no deferred, means it was the last ditch
-        // timeout-based check, so check all waiting require deferreds.
-        if (d) {
-          if (!d.finished) {
-            breakCycle(d, {}, {});
-          }
-        } else if (requireDeferreds.length) {
-          requireDeferreds.forEach(function (d) {
-            breakCycle(d, {}, {});
-          });
-        }
-      }
-
-      // If still waiting on loads, and the waiting load is something
-      // other than a plugin resource, or there are still outstanding
-      // scripts, then just try back later.
-      if (expired) {
-        // If wait time expired, throw error of unloaded modules.
-        for (mid in deferreds) {
-          dfd = deferreds[mid];
-          if (!dfd.finished) {
-            notFinished.push(dfd.map.id);
-          }
-        }
-        err = new Error('Timeout for modules: ' + notFinished);
-        err.requireModules = notFinished;
-        req.onError(err);
-      } else if (loadCount || requireDeferreds.length) {
-        // Something is still waiting to load. Wait for it, but only
-        // if a later check is not already scheduled. Using setTimeout
-        // because want other things in the event loop to happen,
-        // to help in dependency resolution, and this is really a
-        // last ditch check, mostly for detecting timeouts (cycles
-        // should come through the main() use of check()), so it can
-        // wait a bit before doing the final check.
-        if (!checkingLater) {
-          checkingLater = true;
-          setTimeout(function () {
-            checkingLater = false;
-            check();
-          }, 70);
-        }
-      }
-    }
-
-    // Used to break out of the promise try/catch chains.
-    function delayedError(e) {
-      setTimeout(function () {
-        if (!e.dynaId || !trackedErrors[e.dynaId]) {
-          trackedErrors[e.dynaId] = true;
-          req.onError(e);
-        }
-      });
-      return e;
-    }
-
-    main = function (name, deps, factory, errback, relName) {
-      if (name) {
-        // Only allow main calling once per module.
-        if (name in calledDefine) {
-          return;
-        }
-        calledDefine[name] = true;
-      }
-
-      var d = getDefer(name);
-
-      // This module may not have dependencies
-      if (deps && !Array.isArray(deps)) {
-        // deps is not an array, so probably means
-        // an object literal or factory function for
-        // the value. Adjust args.
-        factory = deps;
-        deps = [];
-      }
-
-      // Create fresh array instead of modifying passed in value.
-      deps = deps ? slice.call(deps, 0) : null;
-
-      if (!errback) {
-        if (hasProp(config, 'defaultErrback')) {
-          if (config.defaultErrback) {
-            errback = config.defaultErrback;
-          }
-        } else {
-          errback = delayedError;
-        }
-      }
-
-      if (errback) {
-         d.promise.catch(errback);
-      }
-
-      // Use name if no relName
-      relName = relName || name;
-
-      // Call the factory to define the module, if necessary.
-      if (typeof factory === 'function') {
-
-        if (!deps.length && factory.length) {
-          // Remove comments from the callback string,
-          // look for require calls, and pull them into the dependencies,
-          // but only if there are function args.
-          factory
-            .toString()
-            .replace(commentRegExp, commentReplace)
-            .replace(cjsRequireRegExp, function (match, dep) {
-              deps.push(dep);
-            });
-
-          // May be a CommonJS thing even without require calls, but still
-          // could use exports, and module. Avoid doing exports and module
-          // work though if it just needs require.
-          // REQUIRES the function to expect the CommonJS variables in the
-          // order listed below.
-          deps = (factory.length === 1 ?
-              ['require'] :
-              ['require', 'exports', 'module']).concat(deps);
-        }
-
-        // Save info for use later.
-        d.factory = factory;
-        d.deps = deps;
-
-        d.depending = true;
-        deps.forEach(function (depName, i) {
-          var depMap;
-          deps[i] = depMap = makeMap(depName, relName, true);
-          depName = depMap.id;
-
-          // Fast path CommonJS standard dependencies.
-          if (depName === "require") {
-            d.values[i] = handlers.require(name);
-          } else if (depName === "exports") {
-            // CommonJS module spec 1.1
-            d.values[i] = handlers.exports(name);
-            d.usingExports = true;
-          } else if (depName === "module") {
-            // CommonJS module spec 1.1
-            d.values[i] = d.cjsModule = handlers.module(name);
-          } else if (depName === undefined) {
-            d.values[i] = undefined;
-          } else {
-            waitForDep(depMap, relName, d, i);
-          }
-        });
-        d.depending = false;
-
-        // Some modules just depend on the require, exports, modules, so
-        // trigger their definition here if so.
-        if (d.depCount === d.depMax) {
-          defineModule(d);
-        }
-      } else if (name) {
-        // May just be an object definition for the module. Only
-        // worry about defining if have a module name.
-        resolve(name, d, factory);
-      }
-
-      startTime = (new Date()).getTime();
-
-      if (!name) {
-        check(d);
-      }
-
-      return d.promise;
-    };
-
-    req = makeRequire(null, true);
-
-    /*
-     * Just drops the config on the floor, but returns req in case
-     * the config return value is used.
-     */
-    req.config = function (cfg) {
-      if (cfg.context && cfg.context !== contextName) {
-        var existingContext = getOwn(contexts, cfg.context);
-        if (existingContext) {
-          return existingContext.req.config(cfg);
-        } else {
-          return newContext(cfg.context).config(cfg);
-        }
-      }
-
-      // Since config changed, mapCache may not be valid any more.
-      mapCache = obj();
-
-      // Make sure the baseUrl ends in a slash.
-      if (cfg.baseUrl) {
-        if (cfg.baseUrl.charAt(cfg.baseUrl.length - 1) !== '/') {
-          cfg.baseUrl += '/';
-        }
-      }
-
-      // Convert old style urlArgs string to a function.
-      if (typeof cfg.urlArgs === 'string') {
-        var urlArgs = cfg.urlArgs;
-        cfg.urlArgs = function(id, url) {
-          return (url.indexOf('?') === -1 ? '?' : '&') + urlArgs;
-        };
-      }
-
-      // Save off the paths and packages since they require special processing,
-      // they are additive.
-      var shim = config.shim,
-        objs = {
-          paths: true,
-          bundles: true,
-          config: true,
-          map: true
-        };
-
-      eachProp(cfg, function (value, prop) {
-        if (objs[prop]) {
-          if (!config[prop]) {
-            config[prop] = {};
-          }
-          mixin(config[prop], value, true, true);
-        } else {
-          config[prop] = value;
-        }
-      });
-
-      // Reverse map the bundles
-      if (cfg.bundles) {
-        eachProp(cfg.bundles, function (value, prop) {
-          value.forEach(function (v) {
-            if (v !== prop) {
-              bundlesMap[v] = prop;
-            }
-          });
-        });
-      }
-
-      // Merge shim
-      if (cfg.shim) {
-        eachProp(cfg.shim, function (value, id) {
-          // Normalize the structure
-          if (Array.isArray(value)) {
-            value = {
-              deps: value
-            };
-          }
-          if ((value.exports || value.init) && !value.exportsFn) {
-            value.exportsFn = makeShimExports(value);
-          }
-          shim[id] = value;
-        });
-        config.shim = shim;
-      }
-
-      // Adjust packages if necessary.
-      if (cfg.packages) {
-        cfg.packages.forEach(function (pkgObj) {
-          var location, name;
-
-          pkgObj = typeof pkgObj === 'string' ? { name: pkgObj } : pkgObj;
-
-          name = pkgObj.name;
-          location = pkgObj.location;
-          if (location) {
-            config.paths[name] = pkgObj.location;
-          }
-
-          // Save pointer to main module ID for pkg name.
-          // Remove leading dot in main, so main paths are normalized,
-          // and remove any trailing .js, since different package
-          // envs have different conventions: some use a module name,
-          // some use a file name.
-          config.pkgs[name] = pkgObj.name + '/' + (pkgObj.main || 'main')
-                 .replace(currDirRegExp, '')
-                 .replace(jsSuffixRegExp, '');
-        });
-      }
-
-      // If a deps array or a config callback is specified, then call
-      // require with those args. This is useful when require is defined as a
-      // config object before require.js is loaded.
-      if (cfg.deps || cfg.callback) {
-        req(cfg.deps, cfg.callback);
-      }
-
-      return req;
-    };
-
-    req.onError = function (err) {
-      throw err;
-    };
-
-    context = {
-      id: contextName,
-      defined: defined,
-      waiting: waiting,
-      config: config,
-      deferreds: deferreds,
-      req: req,
-      execCb: function execCb(name, callback, args, exports) {
-        return callback.apply(exports, args);
-      }
-    };
-
-    contexts[contextName] = context;
-
-    return req;
-  }
-
-  requirejs = topReq = newContext('_');
-
-  if (typeof require !== 'function') {
-    require = topReq;
-  }
-
-  /**
-   * Executes the text. Normally just uses eval, but can be modified
-   * to use a better, environment-specific call. Only used for transpiling
-   * loader plugins, not for plain JS modules.
-   * @param {String} text the text to execute/evaluate.
-   */
-  topReq.exec = function (text) {
-    /*jslint evil: true */
-    return eval(text);
-  };
-
-  topReq.contexts = contexts;
-
-  define = function () {
-    queue.push(slice.call(arguments, 0));
-  };
-
-  define.amd = {
-    jQuery: true
-  };
-
-  if (bootstrapConfig) {
-    topReq.config(bootstrapConfig);
-  }
-
-  // data-main support.
-  if (topReq.isBrowser && !contexts._.config.skipDataMain) {
-    dataMain = document.querySelectorAll('script[data-main]')[0];
-    dataMain = dataMain && dataMain.getAttribute('data-main');
-    if (dataMain) {
-      // Strip off any trailing .js since dataMain is now
-      // like a module name.
-      dataMain = dataMain.replace(jsSuffixRegExp, '');
-
-      // Set final baseUrl if there is not already an explicit one,
-      // but only do so if the data-main value is not a loader plugin
-      // module ID.
-      if ((!bootstrapConfig || !bootstrapConfig.baseUrl) &&
-          dataMain.indexOf('!') === -1) {
-        // Pull off the directory of data-main for use as the
-        // baseUrl.
-        src = dataMain.split('/');
-        dataMain = src.pop();
-        subPath = src.length ? src.join('/')  + '/' : './';
-
-        topReq.config({baseUrl: subPath});
-      }
-
-      topReq([dataMain]);
-    }
-  }
-}(this, (typeof Promise !== 'undefined' ? Promise : undefined)));
-
-define("requireLib", function(){});
-
-//noinspection JSUnresolvedVariable
-requirejs.config({
-       paths: {
-               enquire: '3rdParty/enquire',
-               favico: '3rdParty/favico',
-               'perfect-scrollbar': '3rdParty/perfect-scrollbar',
-               'Pica': '3rdParty/pica',
-               prism: '3rdParty/prism',
-       },
-       shim: {
-               enquire: { exports: 'enquire' },
-               favico: { exports: 'Favico' },
-               'perfect-scrollbar': { exports: 'PerfectScrollbar' }
-       },
-       map: {
-               '*': {
-                       'Ajax': 'WoltLabSuite/Core/Ajax',
-                       'AjaxJsonp': 'WoltLabSuite/Core/Ajax/Jsonp',
-                       'AjaxRequest': 'WoltLabSuite/Core/Ajax/Request',
-                       'CallbackList': 'WoltLabSuite/Core/CallbackList',
-                       'ColorUtil': 'WoltLabSuite/Core/ColorUtil',
-                       'Core': 'WoltLabSuite/Core/Core',
-                       'DateUtil': 'WoltLabSuite/Core/Date/Util',
-                       'Devtools': 'WoltLabSuite/Core/Devtools',
-                       'Dictionary': 'WoltLabSuite/Core/Dictionary',
-                       'Dom/ChangeListener': 'WoltLabSuite/Core/Dom/Change/Listener',
-                       'Dom/Traverse': 'WoltLabSuite/Core/Dom/Traverse',
-                       'Dom/Util': 'WoltLabSuite/Core/Dom/Util',
-                       'Environment': 'WoltLabSuite/Core/Environment',
-                       'EventHandler': 'WoltLabSuite/Core/Event/Handler',
-                       'EventKey': 'WoltLabSuite/Core/Event/Key',
-                       'Language': 'WoltLabSuite/Core/Language',
-                       'List': 'WoltLabSuite/Core/List',
-                       'ObjectMap': 'WoltLabSuite/Core/ObjectMap',
-                       'Permission': 'WoltLabSuite/Core/Permission',
-                       'StringUtil': 'WoltLabSuite/Core/StringUtil',
-                       'Ui/Alignment': 'WoltLabSuite/Core/Ui/Alignment',
-                       'Ui/CloseOverlay': 'WoltLabSuite/Core/Ui/CloseOverlay',
-                       'Ui/Confirmation': 'WoltLabSuite/Core/Ui/Confirmation',
-                       'Ui/Dialog': 'WoltLabSuite/Core/Ui/Dialog',
-                       'Ui/Notification': 'WoltLabSuite/Core/Ui/Notification',
-                       'Ui/ReusableDropdown': 'WoltLabSuite/Core/Ui/Dropdown/Reusable',
-                       'Ui/Screen': 'WoltLabSuite/Core/Ui/Screen',
-                       'Ui/Scroll': 'WoltLabSuite/Core/Ui/Scroll',
-                       'Ui/SimpleDropdown': 'WoltLabSuite/Core/Ui/Dropdown/Simple',
-                       'Ui/TabMenu': 'WoltLabSuite/Core/Ui/TabMenu',
-                       'Upload': 'WoltLabSuite/Core/Upload',
-                       'User': 'WoltLabSuite/Core/User'
-               }
-       },
-       waitSeconds: 0
-});
-
-/* Define jQuery shim. We cannot use the shim object in the configuration above,
-   because it tries to load the file, even if the exported global already exists.
-   This shim is needed for jQuery plugins supporting an AMD loaded jQuery, because
-   we break the AMD support of jQuery for BC reasons.
-*/
-define('jquery', [],function() {
-       return window.jQuery;
-});
-
-
-define("require.config", function(){});
-
-/**
- * Collection of global short hand functions.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- */
-(function(window, document) {
-       /**
-        * Shorthand function to retrieve or set an attribute.
-        * 
-        * @param       {Element}       element         target element
-        * @param       {string}        attribute       attribute name
-        * @param       {?=}            value           attribute value, omit if attribute should be read
-        * @return      {(string|undefined)}            attribute value, empty string if attribute is not set or undefined if `value` was omitted
-        */
-       window.elAttr = function(element, attribute, value) {
-               if (value === undefined) {
-                       return element.getAttribute(attribute) || '';
-               }
-               
-               element.setAttribute(attribute, value);
-       };
-       
-       /**
-        * Shorthand function to retrieve a boolean attribute.
-        * 
-        * @param       {Element}       element         target element
-        * @param       {string}        attribute       attribute name
-        * @return      {boolean}       true if value is either `1` or `true`
-        */
-       window.elAttrBool = function(element, attribute) {
-               var value = elAttr(element, attribute);
-               
-               return (value === "1" || value === "true");
-       };
-       
-       /**
-        * Shorthand function to find elements by class name.
-        * 
-        * @param       {string}        className       CSS class name
-        * @param       {Element=}      context         target element, assuming `document` if omitted
-        * @return      {NodeList}      matching elements
-        */
-       window.elByClass = function(className, context) {
-               return (context || document).getElementsByClassName(className);
-       };
-       
-       /**
-        * Shorthand function to retrieve an element by id.
-        * 
-        * @param       {string}        id      element id
-        * @return      {(Element|null)}        matching element or null if not found
-        */
-       window.elById = function(id) {
-               return document.getElementById(id);
-       };
-       
-       /**
-        * Shorthand function to find an element by CSS selector.
-        * 
-        * @param       {string}        selector        CSS selector
-        * @param       {Element=}      context         target element, assuming `document` if omitted
-        * @return      {(Element|null)}                matching element or null if no match
-        */
-       window.elBySel = function(selector, context) {
-               return (context || document).querySelector(selector);
-       };
-       
-       /**
-        * Shorthand function to find elements by CSS selector.
-        * 
-        * @param       {string}        selector        CSS selector
-        * @param       {Element=}      context         target element, assuming `document` if omitted
-        * @param       {function=}     callback        callback function passed to forEach()
-        * @return      {NodeList}      matching elements
-        */
-       window.elBySelAll = function(selector, context, callback) {
-               var nodeList = (context || document).querySelectorAll(selector);
-               if (typeof callback === 'function') {
-                       Array.prototype.forEach.call(nodeList, callback);
-               }
-               
-               return nodeList;
-       };
-       
-       /**
-        * Shorthand function to find elements by tag name.
-        * 
-        * @param       {string}        tagName         element tag name
-        * @param       {Element=}      context         target element, assuming `document` if omitted
-        * @return      {NodeList}      matching elements
-        */
-       window.elByTag = function(tagName, context) {
-               return (context || document).getElementsByTagName(tagName);
-       };
-       
-       /**
-        * Shorthand function to create a DOM element.
-        * 
-        * @param       {string}        tagName         element tag name
-        * @return      {Element}       new DOM element
-        */
-       window.elCreate = function(tagName) {
-               return document.createElement(tagName);
-       };
-       
-       /**
-        * Returns the closest element (parent for text nodes), optionally matching
-        * the provided selector.
-        * 
-        * @param       {Node}          node            start node
-        * @param       {string=}       selector        optional CSS selector
-        * @return      {Element}       closest matching element
-        */
-       window.elClosest = function (node, selector) {
-               if (!(node instanceof Node)) {
-                       throw new TypeError('Provided element is not a Node.');
-               }
-               
-               // retrieve the parent element for text nodes
-               if (node.nodeType === Node.TEXT_NODE) {
-                       node = node.parentNode;
-                       
-                       // text node had no parent
-                       if (node === null) return null;
-               }
-               
-               if (typeof selector !== 'string') selector = '';
-               
-               if (selector.length === 0) return node;
-               
-               return node.closest(selector);
-       };
-       
-       /**
-        * Shorthand function to retrieve or set a 'data-' attribute.
-        * 
-        * @param       {Element}       element         target element
-        * @param       {string}        attribute       attribute name
-        * @param       {?=}            value           attribute value, omit if attribute should be read
-        * @return      {(string|undefined)}            attribute value, empty string if attribute is not set or undefined if `value` was omitted
-        */
-       window.elData = function(element, attribute, value) {
-               attribute = 'data-' + attribute;
-               
-               if (value === undefined) {
-                       return element.getAttribute(attribute) || '';
-               }
-               
-               element.setAttribute(attribute, value);
-       };
-       
-       /**
-        * Shorthand function to retrieve a boolean 'data-' attribute.
-        * 
-        * @param       {Element}       element         target element
-        * @param       {string}        attribute       attribute name
-        * @return      {boolean}       true if value is either `1` or `true`
-        */
-       window.elDataBool = function(element, attribute) {
-               var value = elData(element, attribute);
-               
-               return (value === "1" || value === "true");
-       };
-       
-       /**
-        * Shorthand function to hide an element by setting its 'display' value to 'none'.
-        * 
-        * @param       {Element}       element         DOM element
-        */
-       window.elHide = function(element) {
-               element.style.setProperty('display', 'none', '');
-       };
-       
-       /**
-        * Shorthand function to check if given element is hidden by setting its 'display'
-        * value to 'none'.
-        *
-        * @param       {Element}       element         DOM element
-        * @return      {boolean}
-        */
-       window.elIsHidden = function(element) {
-               return element.style.getPropertyValue('display') === 'none';
-       }
-       
-       /**
-        * Displays or removes an error message below the provided element.
-        * 
-        * @param       {Element}       element         DOM element
-        * @param       {string?}       errorMessage    error message; `false`, `null` and `undefined` are treated as an empty string
-        * @param       {boolean?}      isHtml          defaults to false, causes `errorMessage` to be treated as text only
-        * @return      {?Element}      the inner error element or null if it was removed
-        */
-       window.elInnerError = function (element, errorMessage, isHtml) {
-               var parent = element.parentNode;
-               if (parent === null) {
-                       throw new Error('Only elements that have a parent element or document are valid.');
-               }
-               
-               if (typeof errorMessage !== 'string') {
-                       if (errorMessage === undefined || errorMessage === null || errorMessage === false) {
-                               errorMessage = '';
-                       }
-                       else {
-                               throw new TypeError('The error message must be a string; `false`, `null` or `undefined` can be used as a substitute for an empty string.');
-                       }
-               }
-               
-               var innerError = element.nextElementSibling;
-               if (innerError === null || innerError.nodeName !== 'SMALL' || !innerError.classList.contains('innerError')) {
-                       if (errorMessage === '') {
-                               innerError = null;
-                       }
-                       else {
-                               innerError = elCreate('small');
-                               innerError.className = 'innerError';
-                               parent.insertBefore(innerError, element.nextSibling);
-                       }
-               }
-               
-               if (errorMessage === '') {
-                       if (innerError !== null) {
-                               parent.removeChild(innerError);
-                               innerError = null;
-                       }
-               }
-               else {
-                       innerError[(isHtml ? 'innerHTML' : 'textContent')] = errorMessage;
-               }
-               
-               return innerError;
-       };
-       
-       /**
-        * Shorthand function to remove an element.
-        * 
-        * @param       {Node}          element         DOM node
-        */
-       window.elRemove = function(element) {
-               element.parentNode.removeChild(element);
-       };
-       
-       /**
-        * Shorthand function to show an element previously hidden by using `elHide()`.
-        * 
-        * @param       {Element}       element         DOM element
-        */
-       window.elShow = function(element) {
-               element.style.removeProperty('display');
-       };
-       
-       /**
-        * Toggles visibility of an element using the display style.
-        * 
-        * @param       {Element}       element         DOM element
-        */
-       window.elToggle = function (element) {
-               if (element.style.getPropertyValue('display') === 'none') {
-                       elShow(element);
-               }
-               else {
-                       elHide(element);
-               }
-       };
-       
-       /**
-        * Shorthand function to iterative over an array-like object, arguments passed are the value and the index second.
-        * 
-        * Do not use this function if a simple `for()` is enough or `list` is a plain object.
-        * 
-        * @param       {object}        list            array-like object
-        * @param       {function}      callback        callback function
-        */
-       window.forEach = function(list, callback) {
-               for (var i = 0, length = list.length; i < length; i++) {
-                       callback(list[i], i);
-               }
-       };
-       
-       /**
-        * Shorthand function to check if an object has a property while ignoring the chain.
-        * 
-        * @param       {object}        obj             target object
-        * @param       {string}        property        property name
-        * @return      {boolean}       false if property does not exist or belongs to the chain
-        */
-       window.objOwns = function(obj, property) {
-               return obj.hasOwnProperty(property);
-       };
-       
-       /* assigns a global constant defining the proper 'click' event depending on the browser,
-          enforcing 'touchstart' on mobile devices for a better UX. We're using defineProperty()
-          here because at the time of writing Safari does not support 'const'. Thanks Safari.
-        */
-       var clickEvent = ('touchstart' in document.documentElement || 'ontouchstart' in window || navigator.MaxTouchPoints > 0 || navigator.msMaxTouchPoints > 0) ? 'touchstart' : 'click';
-       Object.defineProperty(window, 'WCF_CLICK_EVENT', {
-               value: 'click' //clickEvent
-       });
-       
-       /* Overwrites any history states after 'initial' with 'skip' on initial page load.
-          This is done, as the necessary DOM of other history states may not exist any more.
-          On forward navigation these 'skip' states are automatically skipped, otherwise the
-          user might have to press the forward button several times.
-          Note: A 'skip' state cannot be hit in the 'popstate' event when navigation backwards,
-                because the history already is left of all the 'skip' states for the current page.
-          Note 2: Setting the URL component of `history.replaceState()` to an empty string will
-                  cause the Internet Explorer to discard the path and query string from the
-                  address bar.
-        */
-       (function() {
-               var stateDepth = 0;
-               function check() {
-                       if (window.history.state && window.history.state.name && window.history.state.name !== 'initial') {
-                               window.history.replaceState({
-                                       name: 'skip',
-                                       depth: ++stateDepth
-                               }, '');
-                               window.history.back();
-                               
-                               // window.history does not update in this iteration of the event loop
-                               setTimeout(check, 1);
-                       }
-                       else {
-                               window.history.replaceState({name: 'initial'}, '');
-                       }
-               }
-               check();
-               
-               window.addEventListener('popstate', function(event) {
-                       if (event.state && event.state.name && event.state.name === 'skip') {
-                               window.history.go(event.state.depth);
-                       }
-               });
-       })();
-       
-       /**
-        * Provides a hashCode() method for strings, similar to Java's String.hashCode().
-        *
-        * @see http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
-        */
-       window.String.prototype.hashCode = function() {
-               var $char;
-               var $hash = 0;
-               
-               if (this.length) {
-                       for (var $i = 0, $length = this.length; $i < $length; $i++) {
-                               $char = this.charCodeAt($i);
-                               $hash = (($hash << 5) - $hash) + $char;
-                               $hash = $hash & $hash; // convert to 32bit integer
-                       }
-               }
-               
-               return $hash;
-       };
-})(window, document);
-
-define("wcf.globalHelper", function(){});
-
-/**
- * Provides the basic core functionality.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Core
- */
-define('WoltLabSuite/Core/Core',[], function() {
-       "use strict";
-       
-       var _clone = function(variable) {
-               if (typeof variable === 'object' && (Array.isArray(variable) || Core.isPlainObject(variable))) {
-                       return _cloneObject(variable);
-               }
-               
-               return variable;
-       };
-       
-       var _cloneObject = function(obj) {
-               if (!obj) {
-                       return null;
-               }
-               
-               if (Array.isArray(obj)) {
-                       return obj.slice();
-               }
-               
-               var newObj = {};
-               for (var key in obj) {
-                       if (obj.hasOwnProperty(key) && typeof obj[key] !== 'undefined') {
-                               newObj[key] = _clone(obj[key]);
-                       }
-               }
-               
-               return newObj;
-       };
-       
-       //noinspection JSUnresolvedVariable
-       var _prefix = 'wsc' + window.WCF_PATH.hashCode() + '-';
-       
-       /**
-        * @exports     WoltLabSuite/Core/Core
-        */
-       var Core = {
-               /**
-                * Deep clones an object.
-                * 
-                * @param       {object}        obj     source object
-                * @return      {object}        cloned object
-                */
-               clone: function(obj) {
-                       return _clone(obj);
-               },
-               
-               /**
-                * Converts WCF 2.0-style URLs into the default URL layout.
-                * 
-                * @param       string  url     target url
-                * @return      rewritten url
-                */
-               convertLegacyUrl: function(url) {
-                       return url.replace(/^index\.php\/(.*?)\/\?/, function(match, controller) {
-                               var parts = controller.split(/([A-Z][a-z0-9]+)/);
-                               controller = '';
-                               for (var i = 0, length = parts.length; i < length; i++) {
-                                       var part = parts[i].trim();
-                                       if (part.length) {
-                                               if (controller.length) controller += '-';
-                                               controller += part.toLowerCase();
-                                       }
-                               }
-                               
-                               return 'index.php?' + controller + '/&';
-                       });
-               },
-               
-               /**
-                * Merges objects with the first argument.
-                * 
-                * @param       {object}        out             destination object
-                * @param       {...object}     arguments       variable number of objects to be merged into the destination object
-                * @return      {object}        destination object with all provided objects merged into
-                */
-               extend: function(out) {
-                       out = out || {};
-                       var newObj = this.clone(out);
-                       
-                       for (var i = 1, length = arguments.length; i < length; i++) {
-                               var obj = arguments[i];
-                               
-                               if (!obj) continue;
-                               
-                               for (var key in obj) {
-                                       if (objOwns(obj, key)) {
-                                               if (!Array.isArray(obj[key]) && typeof obj[key] === 'object') {
-                                                       if (this.isPlainObject(obj[key])) {
-                                                               // object literals have the prototype of Object which in return has no parent prototype
-                                                               newObj[key] = this.extend(out[key], obj[key]);
-                                                       }
-                                                       else {
-                                                               newObj[key] = obj[key];
-                                                       }
-                                               }
-                                               else {
-                                                       newObj[key] = obj[key];
-                                               }
-                                       }
-                               }
-                       }
-                       
-                       return newObj;
-               },
-               
-               /**
-                * Inherits the prototype methods from one constructor to another
-                * constructor.
-                * 
-                * Usage:
-                * 
-                * function MyDerivedClass() {}
-                * Core.inherit(MyDerivedClass, TheAwesomeBaseClass, {
-                *      // regular prototype for `MyDerivedClass`
-                *      
-                *      overwrittenMethodFromBaseClass: function(foo, bar) {
-                *              // do stuff
-                *              
-                *              // invoke parent
-                *              MyDerivedClass._super.prototype.overwrittenMethodFromBaseClass.call(this, foo, bar);
-                *      }
-                * });
-                * 
-                * @see https://github.com/nodejs/node/blob/7d14dd9b5e78faabb95d454a79faa513d0bbc2a5/lib/util.js#L697-L735
-                * @param       {function}      constructor             inheriting constructor function
-                * @param       {function}      superConstructor        inherited constructor function
-                * @param       {object=}       propertiesObject        additional prototype properties
-                */
-               inherit: function(constructor, superConstructor, propertiesObject) {
-                       if (constructor === undefined || constructor === null) {
-                               throw new TypeError("The constructor must not be undefined or null.");
-                       }
-                       if (superConstructor === undefined || superConstructor === null) {
-                               throw new TypeError("The super constructor must not be undefined or null.");
-                       }
-                       if (superConstructor.prototype === undefined) {
-                               throw new TypeError("The super constructor must have a prototype.");
-                       }
-                       
-                       constructor._super = superConstructor;
-                       constructor.prototype = Core.extend(Object.create(superConstructor.prototype, {
-                               constructor: {
-                                       configurable: true,
-                                       enumerable: false,
-                                       value: constructor,
-                                       writable: true
-                               }
-                       }), propertiesObject || {});
-               },
-               
-               /**
-                * Returns true if `obj` is an object literal.
-                * 
-                * @param       {*}     obj     target object
-                * @returns     {boolean}       true if target is an object literal
-                */
-               isPlainObject: function(obj) {
-                       if (typeof obj !== 'object' || obj === null || obj.nodeType) {
-                               return false;
-                       }
-                       
-                       return (Object.getPrototypeOf(obj) === Object.prototype);
-               },
-               
-               /**
-                * Returns the object's class name.
-                * 
-                * @param       {object}        obj     target object
-                * @return      {string}        object class name
-                */
-               getType: function(obj) {
-                       return Object.prototype.toString.call(obj).replace(/^\[object (.+)\]$/, '$1');
-               },
-               
-               /**
-                * Returns a RFC4122 version 4 compilant UUID.
-                * 
-                * @see         http://stackoverflow.com/a/2117523
-                * @return      {string}
-                */
-               getUuid: function() {
-                       return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
-                               var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
-                               return v.toString(16);
-                       });
-               },
-               
-               /**
-                * Recursively serializes an object into an encoded URI parameter string.
-                *  
-                * @param       {object}        obj     target object
-                * @param       {string=}       prefix  parameter prefix
-                * @return      {string}        encoded parameter string
-                */
-               serialize: function(obj, prefix) {
-                       var parameters = [];
-                       
-                       for (var key in obj) {
-                               if (objOwns(obj, key)) {
-                                       var parameterKey = (prefix) ? prefix + '[' + key + ']' : key;
-                                       var value = obj[key];
-                                       
-                                       if (typeof value === 'object') {
-                                               parameters.push(this.serialize(value, parameterKey));
-                                       }
-                                       else {
-                                               parameters.push(encodeURIComponent(parameterKey) + '=' + encodeURIComponent(value));
-                                       }
-                               }
-                       }
-                       
-                       return parameters.join('&');
-               },
-               
-               /**
-                * Triggers a custom or built-in event.
-                * 
-                * @param       {Element}       element         target element
-                * @param       {string}        eventName       event name
-                */
-               triggerEvent: function(element, eventName) {
-                       var event;
-                       
-                       try {
-                               event = new Event(eventName, {
-                                       bubbles: true,
-                                       cancelable: true
-                               });
-                       }
-                       catch (e) {
-                               event = document.createEvent('Event');
-                               event.initEvent(eventName, true, true);
-                       }
-                       
-                       element.dispatchEvent(event);
-               },
-               
-               /**
-                * Returns the unique prefix for the localStorage.
-                * 
-                * @return      {string}        prefix for the localStorage
-                */
-               getStoragePrefix: function() {
-                       return _prefix;
-               }
-       };
-       
-       return Core;
-});
-
-/**
- * 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`.
- * 
- * @author     Tim Duesterhus, Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Dictionary
- */
-define('WoltLabSuite/Core/Dictionary',['Core'], function(Core) {
-       "use strict";
-       
-       var _hasMap = objOwns(window, 'Map') && typeof window.Map === 'function';
-       
-       /**
-        * @constructor
-        */
-       function Dictionary() {
-               this._dictionary = (_hasMap) ? new Map() : {};
-       }
-       Dictionary.prototype = {
-               /**
-                * Sets a new key with given value, will overwrite an existing key.
-                * 
-                * @param       {(number|string)}       key     key
-                * @param       {?}                     value   value
-                */
-               set: function(key, value) {
-                       if (typeof key === 'number') key = key.toString();
-                       
-                       if (typeof key !== "string") {
-                               throw new TypeError("Only strings can be used as keys, rejected '" + key + "' (" + typeof key + ").");
-                       }
-                       
-                       if (_hasMap) this._dictionary.set(key, value);
-                       else this._dictionary[key] = value;
-               },
-               
-               /**
-                * Removes a key from the dictionary.
-                * 
-                * @param       {(number|string)}       key     key
-                */
-               'delete': function(key) {
-                       if (typeof key === 'number') key = key.toString();
-                       
-                       if (_hasMap) this._dictionary['delete'](key);
-                       else this._dictionary[key] = undefined;
-               },
-               
-               /**
-                * Returns true if dictionary contains a value for given key and is not undefined.
-                * 
-                * @param       {(number|string)}       key     key
-                * @return      {boolean}       true if key exists and value is not undefined
-                */
-               has: function(key) {
-                       if (typeof key === 'number') key = key.toString();
-                       
-                       if (_hasMap) return this._dictionary.has(key);
-                       else {
-                               return (objOwns(this._dictionary, key) && typeof this._dictionary[key] !== "undefined");
-                       }
-               },
-               
-               /**
-                * Retrieves a value by key, returns undefined if there is no match.
-                * 
-                * @param       {(number|string)}       key     key
-                * @return      {*}
-                */
-               get: function(key) {
-                       if (typeof key === 'number') key = key.toString();
-                       
-                       if (this.has(key)) {
-                               if (_hasMap) return this._dictionary.get(key);
-                               else return this._dictionary[key];
-                       }
-                       
-                       return undefined;
-               },
-               
-               /**
-                * Iterates over the dictionary keys and values, callback function should expect the
-                * value as first parameter and the key name second.
-                * 
-                * @param       {function<*, string>}   callback        callback for each iteration
-                */
-               forEach: function(callback) {
-                       if (typeof callback !== "function") {
-                               throw new TypeError("forEach() expects a callback as first parameter.");
-                       }
-                       
-                       if (_hasMap) {
-                               this._dictionary.forEach(callback);
-                       }
-                       else {
-                               var keys = Object.keys(this._dictionary);
-                               for (var i = 0, length = keys.length; i < length; i++) {
-                                       callback(this._dictionary[keys[i]], keys[i]);
-                               }
-                       }
-               },
-               
-               /**
-                * Merges one or more Dictionary instances into this one.
-                * 
-                * @param       {...Dictionary}         var_args        one or more Dictionary instances
-                */
-               merge: function() {
-                       for (var i = 0, length = arguments.length; i < length; i++) {
-                               var dictionary = arguments[i];
-                               if (!(dictionary instanceof Dictionary)) {
-                                       throw new TypeError("Expected an object of type Dictionary, but argument " + i + " is not.");
-                               }
-                               
-                               dictionary.forEach((function(value, key) {
-                                       this.set(key, value);
-                               }).bind(this));
-                       }
-               },
-               
-               /**
-                * Returns the object representation of the dictionary.
-                * 
-                * @return      {object}        dictionary's object representation
-                */
-               toObject: function() {
-                       if (!_hasMap) return Core.clone(this._dictionary);
-                       
-                       var object = { };
-                       this._dictionary.forEach(function(value, key) {
-                               object[key] = value;
-                       });
-                       
-                       return object;
-               }
-       };
-       
-       /**
-        * Creates a new Dictionary based on the given object.
-        * All properties that are owned by the object will be added
-        * as keys to the resulting Dictionary.
-        * 
-        * @param       {object}        object
-        * @return      {Dictionary}
-        */
-       Dictionary.fromObject = function(object) {
-               var result = new Dictionary();
-               
-               for (var key in object) {
-                       if (objOwns(object, key)) {
-                               result.set(key, object[key]);
-                       }
-               }
-               
-               return result;
-       };
-       
-       Object.defineProperty(Dictionary.prototype, 'size', {
-               enumerable: false,
-               configurable: true,
-               get: function() {
-                       if (_hasMap) {
-                               return this._dictionary.size;
-                       }
-                       else {
-                               return Object.keys(this._dictionary).length;
-                       }
-               }
-       });
-       
-       return Dictionary;
-});
-
-
-
-define('WoltLabSuite/Core/Template.grammar',['require'],function(require){
-var o=function(k,v,o,l){for(o=o||{},l=k.length;l--;o[k[l]]=v);return o},$V0=[2,37],$V1=[5,9,11,12,13,18,19,21,22,23,25,26,27,28,30,31,32,33,35,37,39],$V2=[1,24],$V3=[1,25],$V4=[1,31],$V5=[1,29],$V6=[1,30],$V7=[1,26],$V8=[1,27],$V9=[1,33],$Va=[11,12,15,40,41,45,47,49,50,52],$Vb=[9,11,12,13,18,19,21,23,26,28,30,31,32,33,35,37],$Vc=[11,12,15,40,41,44,45,46,47,49,50,52],$Vd=[18,35,37],$Ve=[12,15];
-var parser = {trace: function trace() { },
-yy: {},
-symbols_: {"error":2,"TEMPLATE":3,"CHUNK_STAR":4,"EOF":5,"CHUNK_STAR_repetition0":6,"CHUNK":7,"PLAIN_ANY":8,"T_LITERAL":9,"COMMAND":10,"T_ANY":11,"T_WS":12,"{if":13,"COMMAND_PARAMETERS":14,"}":15,"COMMAND_repetition0":16,"COMMAND_option0":17,"{/if}":18,"{include":19,"COMMAND_PARAMETER_LIST":20,"{implode":21,"{/implode}":22,"{foreach":23,"COMMAND_option1":24,"{/foreach}":25,"{lang}":26,"{/lang}":27,"{":28,"VARIABLE":29,"{#":30,"{@":31,"{ldelim}":32,"{rdelim}":33,"ELSE":34,"{else}":35,"ELSE_IF":36,"{elseif":37,"FOREACH_ELSE":38,"{foreachelse}":39,"T_VARIABLE":40,"T_VARIABLE_NAME":41,"VARIABLE_repetition0":42,"VARIABLE_SUFFIX":43,"[":44,"]":45,".":46,"(":47,"VARIABLE_SUFFIX_option0":48,")":49,"=":50,"COMMAND_PARAMETER_VALUE":51,"T_QUOTED_STRING":52,"COMMAND_PARAMETERS_repetition_plus0":53,"COMMAND_PARAMETER":54,"$accept":0,"$end":1},
-terminals_: {2:"error",5:"EOF",9:"T_LITERAL",11:"T_ANY",12:"T_WS",13:"{if",15:"}",18:"{/if}",19:"{include",21:"{implode",22:"{/implode}",23:"{foreach",25:"{/foreach}",26:"{lang}",27:"{/lang}",28:"{",30:"{#",31:"{@",32:"{ldelim}",33:"{rdelim}",35:"{else}",37:"{elseif",39:"{foreachelse}",40:"T_VARIABLE",41:"T_VARIABLE_NAME",44:"[",45:"]",46:".",47:"(",49:")",50:"=",52:"T_QUOTED_STRING"},
-productions_: [0,[3,2],[4,1],[7,1],[7,1],[7,1],[8,1],[8,1],[10,7],[10,3],[10,5],[10,6],[10,3],[10,3],[10,3],[10,3],[10,1],[10,1],[34,2],[36,4],[38,2],[29,3],[43,3],[43,2],[43,3],[20,5],[20,3],[51,1],[51,1],[14,1],[54,1],[54,1],[54,1],[54,1],[54,1],[54,1],[54,3],[6,0],[6,2],[16,0],[16,2],[17,0],[17,1],[24,0],[24,1],[42,0],[42,2],[48,0],[48,1],[53,1],[53,2]],
-performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) {
-/* this == yyval */
-
-var $0 = $$.length - 1;
-switch (yystate) {
-case 1:
- return $$[$0-1] + ";"; 
-break;
-case 2:
-
-       var result = $$[$0].reduce(function (carry, item) {
-               if (item.encode && !carry[1]) carry[0] += " + '" + item.value;
-               else if (item.encode && carry[1]) carry[0] += item.value;
-               else if (!item.encode && carry[1]) carry[0] += "' + " + item.value;
-               else if (!item.encode && !carry[1]) carry[0] += " + " + item.value;
-               
-               carry[1] = item.encode;
-               return carry;
-       }, [ "''", false ]);
-       if (result[1]) result[0] += "'";
-       
-       this.$ = result[0];
-
-break;
-case 3: case 4:
-this.$ = { encode: true, value: $$[$0].replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/(\r\n|\n|\r)/g, '\\n') };
-break;
-case 5:
-this.$ = { encode: false, value: $$[$0] };
-break;
-case 8:
-
-               this.$ = "(function() { if (" + $$[$0-5] + ") { return " + $$[$0-3] + "; } " + $$[$0-2].join(' ') + " " + ($$[$0-1] || '') + " return ''; })()";
-       
-break;
-case 9:
-
-               if (!$$[$0-1]['file']) throw new Error('Missing parameter file');
-               
-               this.$ = $$[$0-1]['file'] + ".fetch(v)";
-       
-break;
-case 10:
-
-               if (!$$[$0-3]['from']) throw new Error('Missing parameter from');
-               if (!$$[$0-3]['item']) throw new Error('Missing parameter item');
-               if (!$$[$0-3]['glue']) $$[$0-3]['glue'] = "', '";
-               
-               this.$ = "(function() { return " + $$[$0-3]['from'] + ".map(function(item) { v[" + $$[$0-3]['item'] + "] = item; return " + $$[$0-1] + "; }).join(" + $$[$0-3]['glue'] + "); })()";
-       
-break;
-case 11:
-
-               if (!$$[$0-4]['from']) throw new Error('Missing parameter from');
-               if (!$$[$0-4]['item']) throw new Error('Missing parameter item');
-               
-               this.$ = "(function() {"
-               + "var looped = false, result = '';"
-               + "if (" + $$[$0-4]['from'] + " instanceof Array) {"
-                       + "for (var i = 0; i < " + $$[$0-4]['from'] + ".length; i++) { looped = true;"
-                               + "v[" + $$[$0-4]['key'] + "] = i;"
-                               + "v[" + $$[$0-4]['item'] + "] = " + $$[$0-4]['from'] + "[i];"
-                               + "result += " + $$[$0-2] + ";"
-                       + "}"
-               + "} else {"
-                       + "for (var key in " + $$[$0-4]['from'] + ") {"
-                               + "if (!" + $$[$0-4]['from'] + ".hasOwnProperty(key)) continue;"
-                               + "looped = true;"
-                               + "v[" + $$[$0-4]['key'] + "] = key;"
-                               + "v[" + $$[$0-4]['item'] + "] = " + $$[$0-4]['from'] + "[key];"
-                               + "result += " + $$[$0-2] + ";"
-                       + "}"
-               + "}"
-               + "return (looped ? result : " + ($$[$0-1] || "''") + "); })()"
-       
-break;
-case 12:
-this.$ = "Language.get(" + $$[$0-1] + ", v)";
-break;
-case 13:
-this.$ = "StringUtil.escapeHTML(" + $$[$0-1] + ")";
-break;
-case 14:
-this.$ = "StringUtil.formatNumeric(" + $$[$0-1] + ")";
-break;
-case 15:
-this.$ = $$[$0-1];
-break;
-case 16:
-this.$ = "'{'";
-break;
-case 17:
-this.$ = "'}'";
-break;
-case 18:
-this.$ = "else { return " + $$[$0] + "; }";
-break;
-case 19:
-this.$ = "else if (" + $$[$0-2] + ") { return " + $$[$0] + "; }";
-break;
-case 20:
-this.$ = $$[$0];
-break;
-case 21:
-this.$ = "v['" + $$[$0-1] + "']" + $$[$0].join('');;
-break;
-case 22:
-this.$ = $$[$0-2] + $$[$0-1] + $$[$0];
-break;
-case 23:
-this.$ = "['" + $$[$0] + "']";
-break;
-case 24: case 36:
-this.$ = $$[$0-2] + ($$[$0-1] || '') + $$[$0];
-break;
-case 25:
- this.$ = $$[$0]; this.$[$$[$0-4]] = $$[$0-2]; 
-break;
-case 26:
- this.$ = {}; this.$[$$[$0-2]] = $$[$0]; 
-break;
-case 29:
-this.$ = $$[$0].join('');
-break;
-case 37: case 39: case 45:
-this.$ = [];
-break;
-case 38: case 40: case 46: case 50:
-$$[$0-1].push($$[$0]);
-break;
-case 49:
-this.$ = [$$[$0]];
-break;
-}
-},
-table: [o([5,9,11,12,13,19,21,23,26,28,30,31,32,33],$V0,{3:1,4:2,6:3}),{1:[3]},{5:[1,4]},o([5,18,22,25,27,35,37,39],[2,2],{7:5,8:6,10:8,9:[1,7],11:[1,9],12:[1,10],13:[1,11],19:[1,12],21:[1,13],23:[1,14],26:[1,15],28:[1,16],30:[1,17],31:[1,18],32:[1,19],33:[1,20]}),{1:[2,1]},o($V1,[2,38]),o($V1,[2,3]),o($V1,[2,4]),o($V1,[2,5]),o($V1,[2,6]),o($V1,[2,7]),{11:$V2,12:$V3,14:21,29:28,40:$V4,41:$V5,47:$V6,50:$V7,52:$V8,53:22,54:23},{20:32,41:$V9},{20:34,41:$V9},{20:35,41:$V9},o([9,11,12,13,19,21,23,26,27,28,30,31,32,33],$V0,{6:3,4:36}),{29:37,40:$V4},{29:38,40:$V4},{29:39,40:$V4},o($V1,[2,16]),o($V1,[2,17]),{15:[1,40]},o([15,45,49],[2,29],{29:28,54:41,11:$V2,12:$V3,40:$V4,41:$V5,47:$V6,50:$V7,52:$V8}),o($Va,[2,49]),o($Va,[2,30]),o($Va,[2,31]),o($Va,[2,32]),o($Va,[2,33]),o($Va,[2,34]),o($Va,[2,35]),{11:$V2,12:$V3,14:42,29:28,40:$V4,41:$V5,47:$V6,50:$V7,52:$V8,53:22,54:23},{41:[1,43]},{15:[1,44]},{50:[1,45]},{15:[1,46]},{15:[1,47]},{27:[1,48]},{15:[1,49]},{15:[1,50]},{15:[1,51]},o($Vb,$V0,{6:3,4:52}),o($Va,[2,50]),{49:[1,53]},o($Vc,[2,45],{42:54}),o($V1,[2,9]),{29:57,40:$V4,51:55,52:[1,56]},o([9,11,12,13,19,21,22,23,26,28,30,31,32,33],$V0,{6:3,4:58}),o([9,11,12,13,19,21,23,25,26,28,30,31,32,33,39],$V0,{6:3,4:59}),o($V1,[2,12]),o($V1,[2,13]),o($V1,[2,14]),o($V1,[2,15]),o($Vd,[2,39],{16:60}),o($Va,[2,36]),o([11,12,15,40,41,45,49,50,52],[2,21],{43:61,44:[1,62],46:[1,63],47:[1,64]}),{12:[1,65],15:[2,26]},o($Ve,[2,27]),o($Ve,[2,28]),{22:[1,66]},{24:67,25:[2,43],38:68,39:[1,69]},{17:70,18:[2,41],34:72,35:[1,74],36:71,37:[1,73]},o($Vc,[2,46]),{11:$V2,12:$V3,14:75,29:28,40:$V4,41:$V5,47:$V6,50:$V7,52:$V8,53:22,54:23},{41:[1,76]},{11:$V2,12:$V3,14:78,29:28,40:$V4,41:$V5,47:$V6,48:77,49:[2,47],50:$V7,52:$V8,53:22,54:23},{20:79,41:$V9},o($V1,[2,10]),{25:[1,80]},{25:[2,44]},o([9,11,12,13,19,21,23,25,26,28,30,31,32,33],$V0,{6:3,4:81}),{18:[1,82]},o($Vd,[2,40]),{18:[2,42]},{11:$V2,12:$V3,14:83,29:28,40:$V4,41:$V5,47:$V6,50:$V7,52:$V8,53:22,54:23},o([9,11,12,13,18,19,21,23,26,28,30,31,32,33],$V0,{6:3,4:84}),{45:[1,85]},o($Vc,[2,23]),{49:[1,86]},{49:[2,48]},{15:[2,25]},o($V1,[2,11]),{25:[2,20]},o($V1,[2,8]),{15:[1,87]},{18:[2,18]},o($Vc,[2,22]),o($Vc,[2,24]),o($Vb,$V0,{6:3,4:88}),o($Vd,[2,19])],
-defaultActions: {4:[2,1],68:[2,44],72:[2,42],78:[2,48],79:[2,25],81:[2,20],84:[2,18]},
-parseError: function parseError(str, hash) {
-    if (hash.recoverable) {
-        this.trace(str);
-    } else {
-        function _parseError (msg, hash) {
-            this.message = msg;
-            this.hash = hash;
-        }
-        _parseError.prototype = Error;
-
-        throw new _parseError(str, hash);
-    }
-},
-parse: function parse(input) {
-    var self = this, stack = [0], tstack = [], vstack = [null], lstack = [], table = this.table, yytext = '', yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1;
-    var args = lstack.slice.call(arguments, 1);
-    var lexer = Object.create(this.lexer);
-    var sharedState = { yy: {} };
-    for (var k in this.yy) {
-        if (Object.prototype.hasOwnProperty.call(this.yy, k)) {
-            sharedState.yy[k] = this.yy[k];
-        }
-    }
-    lexer.setInput(input, sharedState.yy);
-    sharedState.yy.lexer = lexer;
-    sharedState.yy.parser = this;
-    if (typeof lexer.yylloc == 'undefined') {
-        lexer.yylloc = {};
-    }
-    var yyloc = lexer.yylloc;
-    lstack.push(yyloc);
-    var ranges = lexer.options && lexer.options.ranges;
-    if (typeof sharedState.yy.parseError === 'function') {
-        this.parseError = sharedState.yy.parseError;
-    } else {
-        this.parseError = Object.getPrototypeOf(this).parseError;
-    }
-    function popStack(n) {
-        stack.length = stack.length - 2 * n;
-        vstack.length = vstack.length - n;
-        lstack.length = lstack.length - n;
-    }
-    _token_stack:
-        var lex = function () {
-            var token;
-            token = lexer.lex() || EOF;
-            if (typeof token !== 'number') {
-                token = self.symbols_[token] || token;
-            }
-            return token;
-        };
-    var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected;
-    while (true) {
-        state = stack[stack.length - 1];
-        if (this.defaultActions[state]) {
-            action = this.defaultActions[state];
-        } else {
-            if (symbol === null || typeof symbol == 'undefined') {
-                symbol = lex();
-            }
-            action = table[state] && table[state][symbol];
-        }
-                    if (typeof action === 'undefined' || !action.length || !action[0]) {
-                var errStr = '';
-                expected = [];
-                for (p in table[state]) {
-                    if (this.terminals_[p] && p > TERROR) {
-                        expected.push('\'' + this.terminals_[p] + '\'');
-                    }
-                }
-                if (lexer.showPosition) {
-                    errStr = 'Parse error on line ' + (yylineno + 1) + ':\n' + lexer.showPosition() + '\nExpecting ' + expected.join(', ') + ', got \'' + (this.terminals_[symbol] || symbol) + '\'';
-                } else {
-                    errStr = 'Parse error on line ' + (yylineno + 1) + ': Unexpected ' + (symbol == EOF ? 'end of input' : '\'' + (this.terminals_[symbol] || symbol) + '\'');
-                }
-                this.parseError(errStr, {
-                    text: lexer.match,
-                    token: this.terminals_[symbol] || symbol,
-                    line: lexer.yylineno,
-                    loc: yyloc,
-                    expected: expected
-                });
-            }
-        if (action[0] instanceof Array && action.length > 1) {
-            throw new Error('Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol);
-        }
-        switch (action[0]) {
-        case 1:
-            stack.push(symbol);
-            vstack.push(lexer.yytext);
-            lstack.push(lexer.yylloc);
-            stack.push(action[1]);
-            symbol = null;
-            if (!preErrorSymbol) {
-                yyleng = lexer.yyleng;
-                yytext = lexer.yytext;
-                yylineno = lexer.yylineno;
-                yyloc = lexer.yylloc;
-                if (recovering > 0) {
-                    recovering--;
-                }
-            } else {
-                symbol = preErrorSymbol;
-                preErrorSymbol = null;
-            }
-            break;
-        case 2:
-            len = this.productions_[action[1]][1];
-            yyval.$ = vstack[vstack.length - len];
-            yyval._$ = {
-                first_line: lstack[lstack.length - (len || 1)].first_line,
-                last_line: lstack[lstack.length - 1].last_line,
-                first_column: lstack[lstack.length - (len || 1)].first_column,
-                last_column: lstack[lstack.length - 1].last_column
-            };
-            if (ranges) {
-                yyval._$.range = [
-                    lstack[lstack.length - (len || 1)].range[0],
-                    lstack[lstack.length - 1].range[1]
-                ];
-            }
-            r = this.performAction.apply(yyval, [
-                yytext,
-                yyleng,
-                yylineno,
-                sharedState.yy,
-                action[1],
-                vstack,
-                lstack
-            ].concat(args));
-            if (typeof r !== 'undefined') {
-                return r;
-            }
-            if (len) {
-                stack = stack.slice(0, -1 * len * 2);
-                vstack = vstack.slice(0, -1 * len);
-                lstack = lstack.slice(0, -1 * len);
-            }
-            stack.push(this.productions_[action[1]][0]);
-            vstack.push(yyval.$);
-            lstack.push(yyval._$);
-            newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
-            stack.push(newState);
-            break;
-        case 3:
-            return true;
-        }
-    }
-    return true;
-}};
-
-/* generated by jison-lex 0.3.4 */
-var lexer = (function(){
-var lexer = ({
-
-EOF:1,
-
-parseError:function parseError(str, hash) {
-        if (this.yy.parser) {
-            this.yy.parser.parseError(str, hash);
-        } else {
-            throw new Error(str);
-        }
-    },
-
-// resets the lexer, sets new input
-setInput:function (input, yy) {
-        this.yy = yy || this.yy || {};
-        this._input = input;
-        this._more = this._backtrack = this.done = false;
-        this.yylineno = this.yyleng = 0;
-        this.yytext = this.matched = this.match = '';
-        this.conditionStack = ['INITIAL'];
-        this.yylloc = {
-            first_line: 1,
-            first_column: 0,
-            last_line: 1,
-            last_column: 0
-        };
-        if (this.options.ranges) {
-            this.yylloc.range = [0,0];
-        }
-        this.offset = 0;
-        return this;
-    },
-
-// consumes and returns one char from the input
-input:function () {
-        var ch = this._input[0];
-        this.yytext += ch;
-        this.yyleng++;
-        this.offset++;
-        this.match += ch;
-        this.matched += ch;
-        var lines = ch.match(/(?:\r\n?|\n).*/g);
-        if (lines) {
-            this.yylineno++;
-            this.yylloc.last_line++;
-        } else {
-            this.yylloc.last_column++;
-        }
-        if (this.options.ranges) {
-            this.yylloc.range[1]++;
-        }
-
-        this._input = this._input.slice(1);
-        return ch;
-    },
-
-// unshifts one char (or a string) into the input
-unput:function (ch) {
-        var len = ch.length;
-        var lines = ch.split(/(?:\r\n?|\n)/g);
-
-        this._input = ch + this._input;
-        this.yytext = this.yytext.substr(0, this.yytext.length - len);
-        //this.yyleng -= len;
-        this.offset -= len;
-        var oldLines = this.match.split(/(?:\r\n?|\n)/g);
-        this.match = this.match.substr(0, this.match.length - 1);
-        this.matched = this.matched.substr(0, this.matched.length - 1);
-
-        if (lines.length - 1) {
-            this.yylineno -= lines.length - 1;
-        }
-        var r = this.yylloc.range;
-
-        this.yylloc = {
-            first_line: this.yylloc.first_line,
-            last_line: this.yylineno + 1,
-            first_column: this.yylloc.first_column,
-            last_column: lines ?
-                (lines.length === oldLines.length ? this.yylloc.first_column : 0)
-                 + oldLines[oldLines.length - lines.length].length - lines[0].length :
-              this.yylloc.first_column - len
-        };
-
-        if (this.options.ranges) {
-            this.yylloc.range = [r[0], r[0] + this.yyleng - len];
-        }
-        this.yyleng = this.yytext.length;
-        return this;
-    },
-
-// When called from action, caches matched text and appends it on next action
-more:function () {
-        this._more = true;
-        return this;
-    },
-
-// When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead.
-reject:function () {
-        if (this.options.backtrack_lexer) {
-            this._backtrack = true;
-        } else {
-            return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n' + this.showPosition(), {
-                text: "",
-                token: null,
-                line: this.yylineno
-            });
-
-        }
-        return this;
-    },
-
-// retain first n characters of the match
-less:function (n) {
-        this.unput(this.match.slice(n));
-    },
-
-// displays already matched input, i.e. for error messages
-pastInput:function () {
-        var past = this.matched.substr(0, this.matched.length - this.match.length);
-        return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
-    },
-
-// displays upcoming input, i.e. for error messages
-upcomingInput:function () {
-        var next = this.match;
-        if (next.length < 20) {
-            next += this._input.substr(0, 20-next.length);
-        }
-        return (next.substr(0,20) + (next.length > 20 ? '...' : '')).replace(/\n/g, "");
-    },
-
-// displays the character position where the lexing error occurred, i.e. for error messages
-showPosition:function () {
-        var pre = this.pastInput();
-        var c = new Array(pre.length + 1).join("-");
-        return pre + this.upcomingInput() + "\n" + c + "^";
-    },
-
-// test the lexed token: return FALSE when not a match, otherwise return token
-test_match:function (match, indexed_rule) {
-        var token,
-            lines,
-            backup;
-
-        if (this.options.backtrack_lexer) {
-            // save context
-            backup = {
-                yylineno: this.yylineno,
-                yylloc: {
-                    first_line: this.yylloc.first_line,
-                    last_line: this.last_line,
-                    first_column: this.yylloc.first_column,
-                    last_column: this.yylloc.last_column
-                },
-                yytext: this.yytext,
-                match: this.match,
-                matches: this.matches,
-                matched: this.matched,
-                yyleng: this.yyleng,
-                offset: this.offset,
-                _more: this._more,
-                _input: this._input,
-                yy: this.yy,
-                conditionStack: this.conditionStack.slice(0),
-                done: this.done
-            };
-            if (this.options.ranges) {
-                backup.yylloc.range = this.yylloc.range.slice(0);
-            }
-        }
-
-        lines = match[0].match(/(?:\r\n?|\n).*/g);
-        if (lines) {
-            this.yylineno += lines.length;
-        }
-        this.yylloc = {
-            first_line: this.yylloc.last_line,
-            last_line: this.yylineno + 1,
-            first_column: this.yylloc.last_column,
-            last_column: lines ?
-                         lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length :
-                         this.yylloc.last_column + match[0].length
-        };
-        this.yytext += match[0];
-        this.match += match[0];
-        this.matches = match;
-        this.yyleng = this.yytext.length;
-        if (this.options.ranges) {
-            this.yylloc.range = [this.offset, this.offset += this.yyleng];
-        }
-        this._more = false;
-        this._backtrack = false;
-        this._input = this._input.slice(match[0].length);
-        this.matched += match[0];
-        token = this.performAction.call(this, this.yy, this, indexed_rule, this.conditionStack[this.conditionStack.length - 1]);
-        if (this.done && this._input) {
-            this.done = false;
-        }
-        if (token) {
-            return token;
-        } else if (this._backtrack) {
-            // recover context
-            for (var k in backup) {
-                this[k] = backup[k];
-            }
-            return false; // rule action called reject() implying the next rule should be tested instead.
-        }
-        return false;
-    },
-
-// return next match in input
-next:function () {
-        if (this.done) {
-            return this.EOF;
-        }
-        if (!this._input) {
-            this.done = true;
-        }
-
-        var token,
-            match,
-            tempMatch,
-            index;
-        if (!this._more) {
-            this.yytext = '';
-            this.match = '';
-        }
-        var rules = this._currentRules();
-        for (var i = 0; i < rules.length; i++) {
-            tempMatch = this._input.match(this.rules[rules[i]]);
-            if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
-                match = tempMatch;
-                index = i;
-                if (this.options.backtrack_lexer) {
-                    token = this.test_match(tempMatch, rules[i]);
-                    if (token !== false) {
-                        return token;
-                    } else if (this._backtrack) {
-                        match = false;
-                        continue; // rule action called reject() implying a rule MISmatch.
-                    } else {
-                        // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
-                        return false;
-                    }
-                } else if (!this.options.flex) {
-                    break;
-                }
-            }
-        }
-        if (match) {
-            token = this.test_match(match, rules[index]);
-            if (token !== false) {
-                return token;
-            }
-            // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
-            return false;
-        }
-        if (this._input === "") {
-            return this.EOF;
-        } else {
-            return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\n' + this.showPosition(), {
-                text: "",
-                token: null,
-                line: this.yylineno
-            });
-        }
-    },
-
-// return next match that has a token
-lex:function lex() {
-        var r = this.next();
-        if (r) {
-            return r;
-        } else {
-            return this.lex();
-        }
-    },
-
-// activates a new lexer condition state (pushes the new lexer condition state onto the condition stack)
-begin:function begin(condition) {
-        this.conditionStack.push(condition);
-    },
-
-// pop the previously active lexer condition state off the condition stack
-popState:function popState() {
-        var n = this.conditionStack.length - 1;
-        if (n > 0) {
-            return this.conditionStack.pop();
-        } else {
-            return this.conditionStack[0];
-        }
-    },
-
-// produce the lexer rule set which is active for the currently active lexer condition state
-_currentRules:function _currentRules() {
-        if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) {
-            return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules;
-        } else {
-            return this.conditions["INITIAL"].rules;
-        }
-    },
-
-// return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available
-topState:function topState(n) {
-        n = this.conditionStack.length - 1 - Math.abs(n || 0);
-        if (n >= 0) {
-            return this.conditionStack[n];
-        } else {
-            return "INITIAL";
-        }
-    },
-
-// alias for begin(condition)
-pushState:function pushState(condition) {
-        this.begin(condition);
-    },
-
-// return the number of states currently on the stack
-stateStackSize:function stateStackSize() {
-        return this.conditionStack.length;
-    },
-options: {},
-performAction: function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
-var YYSTATE=YY_START;
-switch($avoiding_name_collisions) {
-case 0:/* comment */
-break;
-case 1: yy_.yytext = yy_.yytext.substring(9, yy_.yytext.length - 10); return 9; 
-break;
-case 2:return 52;
-break;
-case 3:return 52;
-break;
-case 4:return 40;
-break;
-case 5: return 41; 
-break;
-case 6:return 46;
-break;
-case 7:return 44;
-break;
-case 8:return 45;
-break;
-case 9:return 47;
-break;
-case 10:return 49;
-break;
-case 11:return 50;
-break;
-case 12:return 32;
-break;
-case 13:return 33;
-break;
-case 14: this.begin('command'); return 30; 
-break;
-case 15: this.begin('command'); return 31; 
-break;
-case 16: this.begin('command'); return 13; 
-break;
-case 17: this.begin('command'); return 37; 
-break;
-case 18: this.begin('command'); return 37; 
-break;
-case 19:return 35;
-break;
-case 20:return 18;
-break;
-case 21:return 26;
-break;
-case 22:return 27;
-break;
-case 23: this.begin('command'); return 19; 
-break;
-case 24: this.begin('command'); return 21; 
-break;
-case 25:return 22;
-break;
-case 26: this.begin('command'); return 23; 
-break;
-case 27:return 39;
-break;
-case 28:return 25;
-break;
-case 29: this.begin('command'); return 28; 
-break;
-case 30: this.popState(); return 15;
-break;
-case 31:return 12;
-break;
-case 32:return 5;
-break;
-case 33:return 11;
-break;
-}
-},
-rules: [/^(?:\{\*[\s\S]*?\*\})/,/^(?:\{literal\}[\s\S]*?\{\/literal\})/,/^(?:"([^"]|\\\.)*")/,/^(?:'([^']|\\\.)*')/,/^(?:\$)/,/^(?:[_a-zA-Z][_a-zA-Z0-9]*)/,/^(?:\.)/,/^(?:\[)/,/^(?:\])/,/^(?:\()/,/^(?:\))/,/^(?:=)/,/^(?:\{ldelim\})/,/^(?:\{rdelim\})/,/^(?:\{#)/,/^(?:\{@)/,/^(?:\{if )/,/^(?:\{else if )/,/^(?:\{elseif )/,/^(?:\{else\})/,/^(?:\{\/if\})/,/^(?:\{lang\})/,/^(?:\{\/lang\})/,/^(?:\{include )/,/^(?:\{implode )/,/^(?:\{\/implode\})/,/^(?:\{foreach )/,/^(?:\{foreachelse\})/,/^(?:\{\/foreach\})/,/^(?:\{(?!\s))/,/^(?:\})/,/^(?:\s+)/,/^(?:$)/,/^(?:[^{])/],
-conditions: {"command":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33],"inclusive":true},"INITIAL":{"rules":[0,1,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,31,32,33],"inclusive":true}}
-});
-return lexer;
-})();
-parser.lexer = lexer;
-return parser;
-});
-/**
- * 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
- */
-define('WoltLabSuite/Core/NumberUtil',[], 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;
-});
-
-/**
- * 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     WoltLabSuite/Core/StringUtil
- */
-define('WoltLabSuite/Core/StringUtil',['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;
-               }
-       };
-});
-
-/**
- * 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('WoltLabSuite/Core/Template',['./Template.grammar', './StringUtil', 'Language'], function(parser, StringUtil, Language) {
-       "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", "v", template).bind(undefined, StringUtil, Language);
-               }
-               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;
-});
-
-/**
- * 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     WoltLabSuite/Core/Language
- */
-define('WoltLabSuite/Core/Language',['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;
-});
-
-/**
- * Simple API to store and invoke multiple callbacks per identifier.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/CallbackList
- */
-define('WoltLabSuite/Core/CallbackList',['Dictionary'], function(Dictionary) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function CallbackList() {
-               this._dictionary = new Dictionary();
-       }
-       CallbackList.prototype = {
-               /**
-                * Adds a callback for given identifier.
-                * 
-                * @param       {string}        identifier      arbitrary string to group and identify callbacks
-                * @param       {function}      callback        callback function
-                */
-               add: function(identifier, callback) {
-                       if (typeof callback !== 'function') {
-                               throw new TypeError("Expected a valid callback as second argument for identifier '" + identifier + "'.");
-                       }
-                       
-                       if (!this._dictionary.has(identifier)) {
-                               this._dictionary.set(identifier, []);
-                       }
-                       
-                       this._dictionary.get(identifier).push(callback);
-               },
-               
-               /**
-                * Removes all callbacks registered for given identifier
-                * 
-                * @param       {string}        identifier      arbitrary string to group and identify callbacks
-                */
-               remove: function(identifier) {
-                       this._dictionary['delete'](identifier);
-               },
-               
-               /**
-                * Invokes callback function on each registered callback.
-                * 
-                * @param       {string|null}           identifier      arbitrary string to group and identify callbacks.
-                *                                                      null is a wildcard to match every identifier
-                * @param       {function(function)}    callback        function called with the individual callback as parameter
-                */
-               forEach: function(identifier, callback) {
-                       if (identifier === null) {
-                               this._dictionary.forEach(function(callbacks, identifier) {
-                                       callbacks.forEach(callback);
-                               });
-                       }
-                       else {
-                               var callbacks = this._dictionary.get(identifier);
-                               if (callbacks !== undefined) {
-                                       callbacks.forEach(callback);
-                               }
-                       }
-               }
-       };
-       
-       return CallbackList;
-});
-
-/**
- * Allows to be informed when the DOM may have changed and
- * new elements that are relevant to you may have been added.
- * 
- * @author     Tim Duesterhus
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Dom/Change/Listener
- */
-define('WoltLabSuite/Core/Dom/Change/Listener',['CallbackList'], function(CallbackList) {
-       "use strict";
-       
-       var _callbackList = new CallbackList();
-       var _hot = false;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Dom/Change/Listener
-        */
-       return {
-               /**
-                * @see WoltLabSuite/Core/CallbackList#add
-                */
-               add: _callbackList.add.bind(_callbackList),
-               
-               /**
-                * @see WoltLabSuite/Core/CallbackList#remove
-                */
-               remove: _callbackList.remove.bind(_callbackList),
-               
-               /**
-                * Triggers the execution of all the listeners.
-                * Use this function when you added new elements to the DOM that might
-                * be relevant to others.
-                * While this function is in progress further calls to it will be ignored.
-                */
-               trigger: function() {
-                       if (_hot) return;
-                       
-                       try {
-                               _hot = true;
-                               _callbackList.forEach(null, function(callback) {
-                                       callback();
-                               });
-                       }
-                       finally {
-                               _hot = false;
-                       }
-               }
-       };
-});
-
-/**
- * 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     WoltLabSuite/Core/Environment
- */
-define('WoltLabSuite/Core/Environment',[], 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);
-               },
-               
-               /**
-                * 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;
-               }
-       };
-});
-
-/**
- * Provides helper functions to work with DOM nodes.
- *
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Dom/Util
- */
-define('WoltLabSuite/Core/Dom/Util',['Environment', 'StringUtil'], function(Environment, StringUtil) {
-       "use strict";
-       
-       function _isBoundaryNode(element, ancestor, position) {
-               if (!ancestor.contains(element)) {
-                       throw new Error("Ancestor element does not contain target element.");
-               }
-               
-               var node, whichSibling = position + 'Sibling';
-               while (element !== null && element !== ancestor) {
-                       if (element[position + 'ElementSibling'] !== null) {
-                               return false;
-                       }
-                       else if (element[whichSibling]) {
-                               node = element[whichSibling];
-                               while (node) {
-                                       if (node.textContent.trim() !== '') {
-                                               return false;
-                                       }
-                                       
-                                       node = node[whichSibling];
-                               }
-                       }
-                       
-                       element = element.parentNode;
-               }
-               
-               return true;
-       }
-       
-       var _idCounter = 0;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Dom/Util
-        */
-       var DomUtil = {
-               /**
-                * Returns a DocumentFragment containing the provided HTML string as DOM nodes.
-                * 
-                * @param       {string}        html    HTML string
-                * @return      {DocumentFragment}      fragment containing DOM nodes
-                */
-               createFragmentFromHtml: function(html) {
-                       var tmp = elCreate('div');
-                       this.setInnerHtml(tmp, html);
-                       
-                       var fragment = document.createDocumentFragment();
-                       while (tmp.childNodes.length) {
-                               fragment.appendChild(tmp.childNodes[0]);
-                       }
-                       
-                       return fragment;
-               },
-               
-               /**
-                * Returns a unique element id.
-                * 
-                * @return      {string}        unique id
-                */
-               getUniqueId: function() {
-                       var elementId;
-                       
-                       do {
-                               elementId = 'wcf' + _idCounter++;
-                       }
-                       while (elById(elementId) !== null);
-                       
-                       return elementId;
-               },
-               
-               /**
-                * Returns the element's id. If there is no id set, a unique id will be
-                * created and assigned.
-                * 
-                * @param       {Element}       el      element
-                * @return      {string}        element id
-                */
-               identify: function(el) {
-                       if (!(el instanceof Element)) {
-                               throw new TypeError("Expected a valid DOM element as argument.");
-                       }
-                       
-                       var id = elAttr(el, 'id');
-                       if (!id) {
-                               id = this.getUniqueId();
-                               elAttr(el, 'id', id);
-                       }
-                       
-                       return id;
-               },
-               
-               /**
-                * Returns the outer height of an element including margins.
-                * 
-                * @param       {Element}               el              element
-                * @param       {CSSStyleDeclaration=}  styles          result of window.getComputedStyle()
-                * @return      {int}                   outer height in px
-                */
-               outerHeight: function(el, styles) {
-                       styles = styles || window.getComputedStyle(el);
-                       
-                       var height = el.offsetHeight;
-                       height += ~~styles.marginTop + ~~styles.marginBottom;
-                       
-                       return height;
-               },
-               
-               /**
-                * Returns the outer width of an element including margins.
-                * 
-                * @param       {Element}               el              element
-                * @param       {CSSStyleDeclaration=}  styles          result of window.getComputedStyle()
-                * @return      {int}   outer width in px
-                */
-               outerWidth: function(el, styles) {
-                       styles = styles || window.getComputedStyle(el);
-                       
-                       var width = el.offsetWidth;
-                       width += ~~styles.marginLeft + ~~styles.marginRight;
-                       
-                       return width;
-               },
-               
-               /**
-                * Returns the outer dimensions of an element including margins.
-                * 
-                * @param       {Element}       el              element
-                * @return      {{height: int, width: int}}     dimensions in px
-                */
-               outerDimensions: function(el) {
-                       var styles = window.getComputedStyle(el);
-                       
-                       return {
-                               height: this.outerHeight(el, styles),
-                               width: this.outerWidth(el, styles)
-                       };
-               },
-               
-               /**
-                * Returns the element's offset relative to the document's top left corner.
-                * 
-                * @param       {Element}       el              element
-                * @return      {{left: int, top: int}}         offset relative to top left corner
-                */
-               offset: function(el) {
-                       var rect = el.getBoundingClientRect();
-                       
-                       return {
-                               top: Math.round(rect.top + (window.scrollY || window.pageYOffset)),
-                               left: Math.round(rect.left + (window.scrollX || window.pageXOffset))
-                       };
-               },
-               
-               /**
-                * Prepends an element to a parent element.
-                * 
-                * @param       {Element}       el              element to prepend
-                * @param       {Element}       parentEl        future containing element
-                */
-               prepend: function(el, parentEl) {
-                       if (parentEl.childNodes.length === 0) {
-                               parentEl.appendChild(el);
-                       }
-                       else {
-                               parentEl.insertBefore(el, parentEl.childNodes[0]);
-                       }
-               },
-               
-               /**
-                * Inserts an element after an existing element.
-                * 
-                * @param       {Element}       newEl           element to insert
-                * @param       {Element}       el              reference element
-                */
-               insertAfter: function(newEl, el) {
-                       if (el.nextSibling !== null) {
-                               el.parentNode.insertBefore(newEl, el.nextSibling);
-                       }
-                       else {
-                               el.parentNode.appendChild(newEl);
-                       }
-               },
-               
-               /**
-                * Applies a list of CSS properties to an element.
-                * 
-                * @param       {Element}               el      element
-                * @param       {Object<string, *>}     styles  list of CSS styles
-                */
-               setStyles: function(el, styles) {
-                       var important = false;
-                       for (var property in styles) {
-                               if (styles.hasOwnProperty(property)) {
-                                       if (/ !important$/.test(styles[property])) {
-                                               important = true;
-                                               
-                                               styles[property] = styles[property].replace(/ !important$/, '');
-                                       }
-                                       else {
-                                               important = false;
-                                       }
-                                       
-                                       // for a set style property with priority = important, some browsers are
-                                       // not able to overwrite it with a property != important; removing the
-                                       // property first solves this issue
-                                       if (el.style.getPropertyPriority(property) === 'important' && !important) {
-                                               el.style.removeProperty(property);
-                                       }
-                                       
-                                       el.style.setProperty(property, styles[property], (important ? 'important' : ''));
-                               }
-                       }
-               },
-               
-               /**
-                * Returns a style property value as integer.
-                * 
-                * The behavior of this method is undefined for properties that are not considered
-                * to have a "numeric" value, e.g. "background-image".
-                * 
-                * @param       {CSSStyleDeclaration}   styles          result of window.getComputedStyle()
-                * @param       {string}                propertyName    property name
-                * @return      {int}                   property value as integer
-                */
-               styleAsInt: function(styles, propertyName) {
-                       var value = styles.getPropertyValue(propertyName);
-                       if (value === null) {
-                               return 0;
-                       }
-                       
-                       return parseInt(value);
-               },
-               
-               /**
-                * Sets the inner HTML of given element and reinjects <script> elements to be properly executed.
-                * 
-                * @see         http://www.w3.org/TR/2008/WD-html5-20080610/dom.html#innerhtml0
-                * @param       {Element}       element         target element
-                * @param       {string}        innerHtml       HTML string
-                */
-               setInnerHtml: function(element, innerHtml) {
-                       element.innerHTML = innerHtml;
-                       
-                       var newScript, script, scripts = elBySelAll('script', element);
-                       for (var i = 0, length = scripts.length; i < length; i++) {
-                               script = scripts[i];
-                               newScript = elCreate('script');
-                               if (script.src) {
-                                       newScript.src = script.src;
-                               }
-                               else {
-                                       newScript.textContent = script.textContent;
-                               }
-                               
-                               element.appendChild(newScript);
-                               elRemove(script);
-                       }
-               },
-               
-               /**
-                * 
-                * @param html
-                * @param {Element} referenceElement
-                * @param insertMethod
-                */
-               insertHtml: function(html, referenceElement, insertMethod) {
-                       var element = elCreate('div');
-                       this.setInnerHtml(element, html);
-                       
-                       if (!element.childNodes.length) {
-                               return;
-                       }
-                       
-                       var node = element.childNodes[0];
-                       switch (insertMethod) {
-                               case 'append':
-                                       referenceElement.appendChild(node);
-                                       break;
-                               
-                               case 'after':
-                                       this.insertAfter(node, referenceElement);
-                                       break;
-                               
-                               case 'prepend':
-                                       this.prepend(node, referenceElement);
-                                       break;
-                               
-                               case 'before':
-                                       referenceElement.parentNode.insertBefore(node, referenceElement);
-                                       break;
-                               
-                               default:
-                                       throw new Error("Unknown insert method '" + insertMethod + "'.");
-                                       break;
-                       }
-                       
-                       var tmp;
-                       while (element.childNodes.length) {
-                               tmp = element.childNodes[0];
-                               
-                               this.insertAfter(tmp, node);
-                               node = tmp;
-                       }
-               },
-               
-               /**
-                * Returns true if `element` contains the `child` element.
-                * 
-                * @param       {Element}       element         container element
-                * @param       {Element}       child           child element
-                * @returns     {boolean}       true if `child` is a (in-)direct child of `element`
-                */
-               contains: function(element, child) {
-                       while (child !== null) {
-                               child = child.parentNode;
-                               
-                               if (element === child) {
-                                       return true;
-                               }
-                       }
-                       
-                       return false;
-               },
-               
-               /**
-                * Retrieves all data attributes from target element, optionally allowing for
-                * a custom prefix that serves two purposes: First it will restrict the results
-                * for items starting with it and second it will remove that prefix.
-                * 
-                * @param       {Element}       element         target element
-                * @param       {string=}       prefix          attribute prefix
-                * @param       {boolean=}      camelCaseName  transform attribute names into camel case using dashes as separators
-                * @param       {boolean=}      idToUpperCase   transform '-id' into 'ID'
-                * @returns     {object<string, string>}        list of data attributes
-                */
-               getDataAttributes: function(element, prefix, camelCaseName, idToUpperCase) {
-                       prefix = prefix || '';
-                       if (!/^data-/.test(prefix)) prefix = 'data-' + prefix;
-                       camelCaseName = (camelCaseName === true);
-                       idToUpperCase = (idToUpperCase === true);
-                       
-                       var attribute, attributes = {}, name, tmp;
-                       for (var i = 0, length = element.attributes.length; i < length; i++) {
-                               attribute = element.attributes[i];
-                               
-                               if (attribute.name.indexOf(prefix) === 0) {
-                                       name = attribute.name.replace(new RegExp('^' + prefix), '');
-                                       if (camelCaseName) {
-                                               tmp = name.split('-');
-                                               name = '';
-                                               for (var j = 0, innerLength = tmp.length; j < innerLength; j++) {
-                                                       if (name.length) {
-                                                               if (idToUpperCase && tmp[j] === 'id') {
-                                                                       tmp[j] = 'ID';
-                                                               }
-                                                               else {
-                                                                       tmp[j] = StringUtil.ucfirst(tmp[j]);
-                                                               }
-                                                       }
-                                                       
-                                                       name += tmp[j];
-                                               }
-                                       }
-                                       
-                                       attributes[name] = attribute.value;
-                               }
-                       }
-                       
-                       return attributes;
-               },
-               
-               /**
-                * Unwraps contained nodes by moving them out of `element` while
-                * preserving their previous order. Target element will be removed
-                * at the end of the operation.
-                * 
-                * @param       {Element}       element         target element
-                */
-               unwrapChildNodes: function(element) {
-                       var parent = element.parentNode;
-                       while (element.childNodes.length) {
-                               parent.insertBefore(element.childNodes[0], element);
-                       }
-                       
-                       elRemove(element);
-               },
-               
-               /**
-                * Replaces an element by moving all child nodes into the new element
-                * while preserving their previous order. The old element will be removed
-                * at the end of the operation.
-                * 
-                * @param       {Element}       oldElement      old element
-                * @param       {Element}       newElement      old element
-                */
-               replaceElement: function(oldElement, newElement) {
-                       while (oldElement.childNodes.length) {
-                               newElement.appendChild(oldElement.childNodes[0]);
-                       }
-                       
-                       oldElement.parentNode.insertBefore(newElement, oldElement);
-                       elRemove(oldElement);
-               },
-               
-               /**
-                * Returns true if given element is the most left node of the ancestor, that is
-                * a node without any content nor elements before it or its parent nodes.
-                * 
-                * @param       {Element}       element         target element
-                * @param       {Element}       ancestor        ancestor element, must contain the target element
-                * @returns     {boolean}       true if target element is the most left node
-                */
-               isAtNodeStart: function(element, ancestor) {
-                       return _isBoundaryNode(element, ancestor, 'previous');
-               },
-               
-               /**
-                * Returns true if given element is the most right node of the ancestor, that is
-                * a node without any content nor elements after it or its parent nodes.
-                * 
-                * @param       {Element}       element         target element
-                * @param       {Element}       ancestor        ancestor element, must contain the target element
-                * @returns     {boolean}       true if target element is the most right node
-                */
-               isAtNodeEnd: function(element, ancestor) {
-                       return _isBoundaryNode(element, ancestor, 'next');
-               },
-               
-               /**
-                * Returns the first ancestor element with position fixed or null.
-                * 
-                * @param       {Element}               element         target element
-                * @returns     {(Element|null)}        first ancestor with position fixed or null
-                */
-               getFixedParent: function (element) {
-                       while (element && element !== document.body) {
-                               if (window.getComputedStyle(element).getPropertyValue('position') === 'fixed') {
-                                       return element;
-                               }
-                               
-                               element = element.offsetParent;
-                       }
-                       
-                       return null;
-               }
-       };
-       
-       // expose on window object for backward compatibility
-       window.bc_wcfDomUtil = DomUtil;
-       
-       return DomUtil;
-});
-
-/**
- * Simple `object` to `object` map using a native WeakMap on supported browsers, otherwise a set of two arrays.
- * 
- * 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     WoltLabSuite/Core/ObjectMap
- */
-define('WoltLabSuite/Core/ObjectMap',[], 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;
-});
-
-/**
- * Provides helper functions to traverse the DOM.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Dom/Traverse
- */
-define('WoltLabSuite/Core/Dom/Traverse',[], function() {
-       "use strict";
-       
-       /** @const */ var NONE = 0;
-       /** @const */ var SELECTOR = 1;
-       /** @const */ var CLASS_NAME = 2;
-       /** @const */ var TAG_NAME = 3;
-       
-       var _probe = [
-               function(el, none) { return true; },
-               function(el, selector) { return el.matches(selector); },
-               function(el, className) { return el.classList.contains(className); },
-               function(el, tagName) { return el.nodeName === tagName; }
-       ];
-       
-       var _children = function(el, type, value) {
-               if (!(el instanceof Element)) {
-                       throw new TypeError("Expected a valid element as first argument.");
-               }
-               
-               var children = [];
-               
-               for (var i = 0; i < el.childElementCount; i++) {
-                       if (_probe[type](el.children[i], value)) {
-                               children.push(el.children[i]);
-                       }
-               }
-               
-               return children;
-       };
-       
-       var _parent = function(el, type, value, untilElement) {
-               if (!(el instanceof Element)) {
-                       throw new TypeError("Expected a valid element as first argument.");
-               }
-               
-               el = el.parentNode;
-               
-               while (el instanceof Element) {
-                       if (el === untilElement) {
-                               return null;
-                       }
-                       
-                       if (_probe[type](el, value)) {
-                               return el;
-                       }
-                       
-                       el = el.parentNode;
-               }
-               
-               return null;
-       };
-       
-       var _sibling = function(el, siblingType, type, value) {
-               if (!(el instanceof Element)) {
-                       throw new TypeError("Expected a valid element as first argument.");
-               }
-               
-               if (el instanceof Element) {
-                       if (el[siblingType] !== null && _probe[type](el[siblingType], value)) {
-                               return el[siblingType];
-                       }
-               }
-               
-               return null;
-       };
-       
-       /**
-        * @exports     WoltLabSuite/Core/Dom/Traverse
-        */
-       return {
-               /**
-                * Examines child elements and returns the first child matching the given selector.
-                * 
-                * @param       {Element}               el              element
-                * @param       {string}                selector        CSS selector to match child elements against
-                * @return      {(Element|null)}        null if there is no child node matching the selector
-                */
-               childBySel: function(el, selector) {
-                       return _children(el, SELECTOR, selector)[0] || null;
-               },
-               
-               /**
-                * Examines child elements and returns the first child that has the given CSS class set.
-                * 
-                * @param       {Element}               el              element
-                * @param       {string}                className       CSS class name
-                * @return      {(Element|null)}        null if there is no child node with given CSS class
-                */
-               childByClass: function(el, className) {
-                       return _children(el, CLASS_NAME, className)[0] || null;
-               },
-               
-               /**
-                * Examines child elements and returns the first child which equals the given tag.
-                * 
-                * @param       {Element}               el              element
-                * @param       {string}                tagName         element tag name
-                * @return      {(Element|null)}        null if there is no child node which equals given tag
-                */
-               childByTag: function(el, tagName) {
-                       return _children(el, TAG_NAME, tagName)[0] || null;
-               },
-               
-               /**
-                * Examines child elements and returns all children matching the given selector.
-                * 
-                * @param       {Element}               el              element
-                * @param       {string}                selector        CSS selector to match child elements against
-                * @return      {array<Element>}        list of children matching the selector
-                */
-               childrenBySel: function(el, selector) {
-                       return _children(el, SELECTOR, selector);
-               },
-               
-               /**
-                * Examines child elements and returns all children that have the given CSS class set.
-                * 
-                * @param       {Element}               el              element
-                * @param       {string}                className       CSS class name
-                * @return      {array<Element>}        list of children with the given class
-                */
-               childrenByClass: function(el, className) {
-                       return _children(el, CLASS_NAME, className);
-               },
-               
-               /**
-                * Examines child elements and returns all children which equal the given tag.
-                * 
-                * @param       {Element}               el              element
-                * @param       {string}                tagName         element tag name
-                * @return      {array<Element>}        list of children equaling the tag name
-                */
-               childrenByTag: function(el, tagName) {
-                       return _children(el, TAG_NAME, tagName);
-               },
-               
-               /**
-                * Examines parent nodes and returns the first parent that matches the given selector.
-                * 
-                * @param       {Element}       el              child element
-                * @param       {string}        selector        CSS selector to match parent nodes against
-                * @param       {Element=}      untilElement    stop when reaching this element
-                * @return      {(Element|null)}        null if no parent node matched the selector
-                */
-               parentBySel: function(el, selector, untilElement) {
-                       return _parent(el, SELECTOR, selector, untilElement);
-               },
-               
-               /**
-                * Examines parent nodes and returns the first parent that has the given CSS class set.
-                * 
-                * @param       {Element}       el              child element
-                * @param       {string}        className       CSS class name
-                * @param       {Element=}      untilElement    stop when reaching this element
-                * @return      {(Element|null)}        null if there is no parent node with given class
-                */
-               parentByClass: function(el, className, untilElement) {
-                       return _parent(el, CLASS_NAME, className, untilElement);
-               },
-               
-               /**
-                * Examines parent nodes and returns the first parent which equals the given tag.
-                * 
-                * @param       {Element}       el              child element
-                * @param       {string}        tagName         element tag name
-                * @param       {Element=}      untilElement    stop when reaching this element
-                * @return      {(Element|null)}        null if there is no parent node of given tag type
-                */
-               parentByTag: function(el, tagName, untilElement) {
-                       return _parent(el, TAG_NAME, tagName, untilElement);
-               },
-               
-               /**
-                * Returns the next element sibling.
-                * 
-                * @param       {Element}       el              element
-                * @return      {(Element|null)}        null if there is no next sibling element
-                */
-               next: function(el) {
-                       return _sibling(el, 'nextElementSibling', NONE, null);
-               },
-               
-               /**
-                * Returns the next element sibling that matches the given selector.
-                * 
-                * @param       {Element}       el              element
-                * @param       {string}        selector        CSS selector to match parent nodes against
-                * @return      {(Element|null)}        null if there is no next sibling element or it does not match the selector
-                */
-               nextBySel: function(el, selector) {
-                       return _sibling(el, 'nextElementSibling', SELECTOR, selector);
-               },
-               
-               /**
-                * Returns the next element sibling with given CSS class.
-                * 
-                * @param       {Element}       el              element
-                * @param       {string}        className       CSS class name
-                * @return      {(Element|null)}        null if there is no next sibling element or it does not have the class set
-                */
-               nextByClass: function(el, className) {
-                       return _sibling(el, 'nextElementSibling', CLASS_NAME, className);
-               },
-               
-               /**
-                * Returns the next element sibling with given CSS class.
-                * 
-                * @param       {Element}       el              element
-                * @param       {string}        tagName         element tag name
-                * @return      {(Element|null)}        null if there is no next sibling element or it does not have the class set
-                */
-               nextByTag: function(el, tagName) {
-                       return _sibling(el, 'nextElementSibling', TAG_NAME, tagName);
-               },
-               
-               /**
-                * Returns the previous element sibling.
-                * 
-                * @param       {Element}       el              element
-                * @return      {(Element|null)}        null if there is no previous sibling element
-                */
-               prev: function(el) {
-                       return _sibling(el, 'previousElementSibling', NONE, null);
-               },
-               
-               /**
-                * Returns the previous element sibling that matches the given selector.
-                * 
-                * @param       {Element}       el              element
-                * @param       {string}        selector        CSS selector to match parent nodes against
-                * @return      {(Element|null)}        null if there is no previous sibling element or it does not match the selector
-                */
-               prevBySel: function(el, selector) {
-                       return _sibling(el, 'previousElementSibling', SELECTOR, selector);
-               },
-               
-               /**
-                * Returns the previous element sibling with given CSS class.
-                * 
-                * @param       {Element}       el              element
-                * @param       {string}        className       CSS class name
-                * @return      {(Element|null)}        null if there is no previous sibling element or it does not have the class set
-                */
-               prevByClass: function(el, className) {
-                       return _sibling(el, 'previousElementSibling', CLASS_NAME, className);
-               },
-               
-               /**
-                * Returns the previous element sibling with given CSS class.
-                * 
-                * @param       {Element}       el              element
-                * @param       {string}        tagName         element tag name
-                * @return      {(Element|null)}        null if there is no previous sibling element or it does not have the class set
-                */
-               prevByTag: function(el, tagName) {
-                       return _sibling(el, 'previousElementSibling', TAG_NAME, tagName);
-               }
-       };
-});
-
-/**
- * Provides the confirmation dialog overlay.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Confirmation
- */
-define('WoltLabSuite/Core/Ui/Confirmation',['Core', 'Language', 'Ui/Dialog'], function(Core, Language, UiDialog) {
-       "use strict";
-       
-       var _active = false;
-       var _confirmButton = null;
-       var _content = null;
-       var _options = {};
-       var _text = null;
-       
-       /**
-        * Confirmation dialog overlay.
-        * 
-        * @exports     WoltLabSuite/Core/Ui/Confirmation
-        */
-       return {
-               /**
-                * Shows the confirmation dialog.
-                * 
-                * Possible options:
-                *  - cancel: callback if user cancels the dialog
-                *  - confirm: callback if user confirm the dialog
-                *  - legacyCallback: WCF 2.0/2.1 compatible callback with string parameter
-                *  - message: displayed confirmation message
-                *  - parameters: list of parameters passed to the callback on confirm
-                *  - template: optional HTML string to be inserted below the `message`
-                * 
-                * @param       {object<string, *>}     options         confirmation options
-                */
-               show: function(options) {
-                       if (UiDialog === undefined) UiDialog = require('Ui/Dialog');
-                       
-                       if (_active) {
-                               return;
-                       }
-                       
-                       _options = Core.extend({
-                               cancel: null,
-                               confirm: null,
-                               legacyCallback: null,
-                               message: '',
-                               messageIsHtml: false,
-                               parameters: {},
-                               template: ''
-                       }, options);
-                       
-                       _options.message = (typeof _options.message === 'string') ? _options.message.trim() : '';
-                       if (!_options.message.length) {
-                               throw new Error("Expected a non-empty string for option 'message'.");
-                       }
-                       
-                       if (typeof _options.confirm !== 'function' && typeof _options.legacyCallback !== 'function') {
-                               throw new TypeError("Expected a valid callback for option 'confirm'.");
-                       }
-                       
-                       if (_content === null) {
-                               this._createDialog();
-                       }
-                       
-                       _content.innerHTML = (typeof _options.template === 'string') ? _options.template.trim() : '';
-                       if (_options.messageIsHtml) _text.innerHTML = _options.message;
-                       else _text.textContent = _options.message;
-                       
-                       _active = true;
-                       
-                       UiDialog.open(this);
-               },
-               
-               _dialogSetup: function() {
-                       return {
-                               id: 'wcfSystemConfirmation',
-                               options: {
-                                       onClose: this._onClose.bind(this),
-                                       onShow: this._onShow.bind(this),
-                                       title: Language.get('wcf.global.confirmation.title')
-                               }
-                       };
-               },
-               
-               /**
-                * Returns content container element.
-                * 
-                * @return      {Element}       content container element
-                */
-               getContentElement: function() {
-                       return _content;
-               },
-               
-               /**
-                * Creates the dialog DOM elements.
-                */
-               _createDialog: function() {
-                       var dialog = elCreate('div');
-                       elAttr(dialog, 'id', 'wcfSystemConfirmation');
-                       dialog.classList.add('systemConfirmation');
-                       
-                       _text = elCreate('p');
-                       dialog.appendChild(_text);
-                       
-                       _content = elCreate('div');
-                       elAttr(_content, 'id', 'wcfSystemConfirmationContent');
-                       dialog.appendChild(_content);
-                       
-                       var formSubmit = elCreate('div');
-                       formSubmit.classList.add('formSubmit');
-                       dialog.appendChild(formSubmit);
-                       
-                       _confirmButton = elCreate('button');
-                       _confirmButton.classList.add('buttonPrimary');
-                       _confirmButton.textContent = Language.get('wcf.global.confirmation.confirm');
-                       _confirmButton.addEventListener(WCF_CLICK_EVENT, this._confirm.bind(this));
-                       formSubmit.appendChild(_confirmButton);
-                       
-                       var cancelButton = elCreate('button');
-                       cancelButton.textContent = Language.get('wcf.global.confirmation.cancel');
-                       cancelButton.addEventListener(WCF_CLICK_EVENT, function() { UiDialog.close('wcfSystemConfirmation'); });
-                       formSubmit.appendChild(cancelButton);
-                       
-                       document.body.appendChild(dialog);
-               },
-               
-               /**
-                * Invoked if the user confirms the dialog.
-                */
-               _confirm: function() {
-                       if (typeof _options.legacyCallback === 'function') {
-                               _options.legacyCallback('confirm', _options.parameters, _content);
-                       }
-                       else {
-                               _options.confirm(_options.parameters, _content);
-                       }
-                       
-                       _active = false;
-                       UiDialog.close('wcfSystemConfirmation');
-               },
-               
-               /**
-                * Invoked on dialog close or if user cancels the dialog.
-                */
-               _onClose: function() {
-                       if (_active) {
-                               _confirmButton.blur();
-                               _active = false;
-                               
-                               if (typeof _options.legacyCallback === 'function') {
-                                       _options.legacyCallback('cancel', _options.parameters, _content);
-                               }
-                               else if (typeof _options.cancel === 'function') {
-                                       _options.cancel(_options.parameters);
-                               }
-                       }
-               },
-               
-               /**
-                * Sets the focus on the confirm button on dialog open for proper keyboard support.
-                */
-               _onShow: function() {
-                       _confirmButton.blur();
-                       _confirmButton.focus();
-               }
-       };
-});
-
-/**
- * Provides consistent support for media queries and body scrolling.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Screen
- */
-define('WoltLabSuite/Core/Ui/Screen',['Core', 'Dictionary', 'Environment'], function(Core, Dictionary, Environment) {
-       "use strict";
-       
-       var _dialogContainer = null;
-       var _mql = new Dictionary();
-       var _scrollDisableCounter = 0;
-       var _scrollOffsetFrom = null;
-       var _scrollTop = 0;
-       var _pageOverlayCounter = 0;
-       
-       var _mqMap = Dictionary.fromObject({
-               'screen-xs': '(max-width: 544px)',                              /* smartphone */
-               'screen-sm': '(min-width: 545px) and (max-width: 768px)',       /* tablet (portrait) */
-               'screen-sm-down': '(max-width: 768px)',                         /* smartphone + tablet (portrait) */
-               'screen-sm-up': '(min-width: 545px)',                           /* tablet (portrait) + tablet (landscape) + desktop */
-               'screen-sm-md': '(min-width: 545px) and (max-width: 1024px)',   /* tablet (portrait) + tablet (landscape) */
-               'screen-md': '(min-width: 769px) and (max-width: 1024px)',      /* tablet (landscape) */
-               'screen-md-down': '(max-width: 1024px)',                        /* smartphone + tablet (portrait) + tablet (landscape) */
-               'screen-md-up': '(min-width: 769px)',                           /* tablet (landscape) + desktop */
-               'screen-lg': '(min-width: 1025px)'                              /* desktop */
-       });
-       
-       // Microsoft Edge rewrites the media queries to whatever it
-       // pleases, causing the input and output query to mismatch
-       var _mqMapEdge = new Dictionary();
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Screen
-        */
-       return {
-               /**
-                * Registers event listeners for media query match/unmatch.
-                * 
-                * The `callbacks` object may contain the following keys:
-                *  - `match`, triggered when media query matches
-                *  - `unmatch`, triggered when media query no longer matches
-                *  - `setup`, invoked when media query first matches
-                * 
-                * Returns a UUID that is used to internal identify the callbacks, can be used
-                * to remove binding by calling the `remove` method.
-                * 
-                * @param       {string}        query           media query
-                * @param       {object}        callbacks       callback functions
-                * @return      {string}        UUID for listener removal
-                */
-               on: function(query, callbacks) {
-                       var uuid = Core.getUuid(), queryObject = this._getQueryObject(query);
-                       
-                       if (typeof callbacks.match === 'function') {
-                               queryObject.callbacksMatch.set(uuid, callbacks.match);
-                       }
-                       
-                       if (typeof callbacks.unmatch === 'function') {
-                               queryObject.callbacksUnmatch.set(uuid, callbacks.unmatch);
-                       }
-                       
-                       if (typeof callbacks.setup === 'function') {
-                               if (queryObject.mql.matches) {
-                                       callbacks.setup();
-                               }
-                               else {
-                                       queryObject.callbacksSetup.set(uuid, callbacks.setup);
-                               }
-                       }
-                       
-                       return uuid;
-               },
-               
-               /**
-                * Removes all listeners identified by their common UUID.
-                *
-                * @param       {string}        query   must match the `query` argument used when calling `on()`
-                * @param       {string}        uuid    UUID received when calling `on()`
-                */
-               remove: function(query, uuid) {
-                       var queryObject = this._getQueryObject(query);
-                       
-                       queryObject.callbacksMatch.delete(uuid);
-                       queryObject.callbacksUnmatch.delete(uuid);
-                       queryObject.callbacksSetup.delete(uuid);
-               },
-               
-               /**
-                * Returns a boolean value if a media query expression currently matches.
-                * 
-                * @param       {string}        query   CSS media query
-                * @returns     {boolean}       true if query matches
-                */
-               is: function(query) {
-                       return this._getQueryObject(query).mql.matches;
-               },
-               
-               /**
-                * Disables scrolling of body element.
-                */
-               scrollDisable: function() {
-                       if (_scrollDisableCounter === 0) {
-                               _scrollTop = document.body.scrollTop;
-                               _scrollOffsetFrom = 'body';
-                               if (!_scrollTop) {
-                                       _scrollTop = document.documentElement.scrollTop;
-                                       _scrollOffsetFrom = 'documentElement';
-                               }
-                               
-                               var pageContainer = elById('pageContainer');
-                               
-                               // setting translateY causes Mobile Safari to snap
-                               if (Environment.platform() === 'ios') {
-                                       pageContainer.style.setProperty('position', 'relative', '');
-                                       pageContainer.style.setProperty('top', '-' + _scrollTop + 'px', '');
-                               }
-                               else {
-                                       pageContainer.style.setProperty('margin-top', '-' + _scrollTop + 'px', '');
-                               }
-                               
-                               document.documentElement.classList.add('disableScrolling');
-                       }
-                       
-                       _scrollDisableCounter++;
-               },
-               
-               /**
-                * Re-enables scrolling of body element.
-                */
-               scrollEnable: function() {
-                       if (_scrollDisableCounter) {
-                               _scrollDisableCounter--;
-                               
-                               if (_scrollDisableCounter === 0) {
-                                       document.documentElement.classList.remove('disableScrolling');
-                                       
-                                       var pageContainer = elById('pageContainer');
-                                       if (Environment.platform() === 'ios') {
-                                               pageContainer.style.removeProperty('position');
-                                               pageContainer.style.removeProperty('top');
-                                       }
-                                       else {
-                                               pageContainer.style.removeProperty('margin-top');
-                                       }
-                                       
-                                       if (_scrollTop) {
-                                               document[_scrollOffsetFrom].scrollTop = ~~_scrollTop;
-                                       }
-                               }
-                       }
-               },
-               
-               /**
-                * Indicates that at least one page overlay is currently open.
-                */
-               pageOverlayOpen: function() {
-                       if (_pageOverlayCounter === 0) {
-                               document.documentElement.classList.add('pageOverlayActive');
-                       }
-                       
-                       _pageOverlayCounter++;
-               },
-               
-               /**
-                * Marks one page overlay as closed.
-                */
-               pageOverlayClose: function() {
-                       if (_pageOverlayCounter) {
-                               _pageOverlayCounter--;
-                               
-                               if (_pageOverlayCounter === 0) {
-                                       document.documentElement.classList.remove('pageOverlayActive');
-                               }
-                       }
-               },
-               
-               /**
-                * Returns true if at least one page overlay is currently open.
-                * 
-                * @returns {boolean}
-                */
-               pageOverlayIsActive: function() {
-                       return _pageOverlayCounter > 0;
-               },
-               
-               /**
-                * Sets the dialog container element. This method is used to
-                * circumvent a possible circular dependency, due to `Ui/Dialog`
-                * requiring the `Ui/Screen` module itself.
-                * 
-                * @param       {Element}       container       dialog container element
-                */
-               setDialogContainer: function (container) {
-                       _dialogContainer = container;
-               },
-               
-               /**
-                * 
-                * @param       {string}        query   CSS media query
-                * @return      {Object}        object containing callbacks and MediaQueryList
-                * @protected
-                */
-               _getQueryObject: function(query) {
-                       if (typeof query !== 'string' || query.trim() === '') {
-                               throw new TypeError("Expected a non-empty string for parameter 'query'.");
-                       }
-                       
-                       // Microsoft Edge rewrites the media queries to whatever it
-                       // pleases, causing the input and output query to mismatch
-                       if (_mqMapEdge.has(query)) query = _mqMapEdge.get(query);
-                       
-                       if (_mqMap.has(query)) query = _mqMap.get(query);
-                       
-                       var queryObject = _mql.get(query);
-                       if (!queryObject) {
-                               queryObject = {
-                                       callbacksMatch: new Dictionary(),
-                                       callbacksUnmatch: new Dictionary(),
-                                       callbacksSetup: new Dictionary(),
-                                       mql: window.matchMedia(query)
-                               };
-                               queryObject.mql.addListener(this._mqlChange.bind(this));
-                               
-                               _mql.set(query, queryObject);
-                               
-                               if (query !== queryObject.mql.media) {
-                                       _mqMapEdge.set(queryObject.mql.media, query);
-                               }
-                       }
-                       
-                       return queryObject;
-               },
-               
-               /**
-                * Triggered whenever a registered media query now matches or no longer matches.
-                * 
-                * @param       {Event} event   event object
-                * @protected
-                */
-               _mqlChange: function(event) {
-                       var queryObject = this._getQueryObject(event.media);
-                       if (event.matches) {
-                               if (queryObject.callbacksSetup.size) {
-                                       queryObject.callbacksSetup.forEach(function(callback) {
-                                               callback();
-                                       });
-                                       
-                                       // discard all setup callbacks after execution
-                                       queryObject.callbacksSetup = new Dictionary();
-                               }
-                               else {
-                                       queryObject.callbacksMatch.forEach(function (callback) {
-                                               callback();
-                                       });
-                               }
-                       }
-                       else {
-                               queryObject.callbacksUnmatch.forEach(function(callback) {
-                                       callback();
-                               });
-                       }
-               }
-       };
-});
-
-/**
- * Provides reliable checks for common key presses, uses `Event.key` on supported browsers
- * or the deprecated `Event.which`.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Event/Key
- */
-define('WoltLabSuite/Core/Event/Key',[], function() {
-       "use strict";
-       
-       function _isKey(event, key, which) {
-               if (!(event instanceof Event)) {
-                       throw new TypeError("Expected a valid event when testing for key '" + key + "'.");
-               }
-               
-               return event.key === key || event.which === which;
-       }
-       
-       /**
-        * @exports     WoltLabSuite/Core/Event/Key
-        */
-       return {
-               /**
-                * Returns true if the pressed key equals 'ArrowDown'.
-                * 
-                * @param       {Event}         event           event object
-                * @return      {boolean}
-                */
-               ArrowDown: function(event) {
-                       return _isKey(event, 'ArrowDown', 40);
-               },
-               
-               /**
-                * Returns true if the pressed key equals 'ArrowLeft'.
-                * 
-                * @param       {Event}         event           event object
-                * @return      {boolean}
-                */
-               ArrowLeft: function(event) {
-                       return _isKey(event, 'ArrowLeft', 37);
-               },
-               
-               /**
-                * Returns true if the pressed key equals 'ArrowRight'.
-                * 
-                * @param       {Event}         event           event object
-                * @return      {boolean}
-                */
-               ArrowRight: function(event) {
-                       return _isKey(event, 'ArrowRight', 39);
-               },
-               
-               /**
-                * Returns true if the pressed key equals 'ArrowUp'.
-                * 
-                * @param       {Event}         event           event object
-                * @return      {boolean}
-                */
-               ArrowUp: function(event) {
-                       return _isKey(event, 'ArrowUp', 38);
-               },
-               
-               /**
-                * Returns true if the pressed key equals 'Comma'.
-                * 
-                * @param       {Event}         event           event object
-                * @return      {boolean}
-                */
-               Comma: function(event) {
-                       return _isKey(event, ',', 44);
-               },
-               
-               /**
-                * Returns true if the pressed key equals 'End'.
-                *
-                * @param       {Event}         event           event object
-                * @return      {boolean}
-                */
-               End: function(event) {
-                       return _isKey(event, 'End', 35);
-               },
-               
-               /**
-                * Returns true if the pressed key equals 'Enter'.
-                * 
-                * @param       {Event}         event           event object
-                * @return      {boolean}
-                */
-               Enter: function(event) {
-                       return _isKey(event, 'Enter', 13);
-               },
-               
-               /**
-                * Returns true if the pressed key equals 'Escape'.
-                * 
-                * @param       {Event}         event           event object
-                * @return      {boolean}
-                */
-               Escape: function(event) {
-                       return _isKey(event, 'Escape', 27);
-               },
-               
-               /**
-                * Returns true if the pressed key equals 'Home'.
-                *
-                * @param       {Event}         event           event object
-                * @return      {boolean}
-                */
-               Home: function(event) {
-                       return _isKey(event, 'Home', 36);
-               },
-               
-               /**
-                * Returns true if the pressed key equals 'Space'.
-                *
-                * @param       {Event}         event           event object
-                * @return      {boolean}
-                */
-               Space: function(event) {
-                       return _isKey(event, 'Space', 32);
-               },
-               
-               /**
-                * Returns true if the pressed key equals 'Tab'.
-                * 
-                * @param       {Event}         event           event object
-                * @return      {boolean}
-                */
-               Tab: function(event) {
-                       return _isKey(event, 'Tab', 9);
-               }
-       };
-});
-
-/**
- * Utility class to align elements relatively to another.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Alignment
- */
-define('WoltLabSuite/Core/Ui/Alignment',['Core', 'Language', 'Dom/Traverse', 'Dom/Util'], function(Core, Language, DomTraverse, DomUtil) {
-       "use strict";
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Alignment
-        */
-       return {
-               /**
-                * Sets the alignment for target element relatively to the reference element.
-                * 
-                * @param       {Element}               el              target element
-                * @param       {Element}               ref             reference element
-                * @param       {Object<string, *>}     options         list of options to alter the behavior
-                */
-               set: function(el, ref, options) {
-                       options = Core.extend({
-                               // offset to reference element
-                               verticalOffset: 0,
-                               
-                               // align the pointer element, expects .elementPointer as a direct child of given element
-                               pointer: false,
-                               
-                               // offset from/left side, ignored for center alignment
-                               pointerOffset: 4,
-                               
-                               // use static pointer positions, expects two items: class to move it to the bottom and the second to move it to the right
-                               pointerClassNames: [],
-                               
-                               // alternate element used to calculate dimensions
-                               refDimensionsElement: null,
-                               
-                               // preferred alignment, possible values: left/right/center and top/bottom
-                               horizontal: 'left',
-                               vertical: 'bottom',
-                               
-                               // allow flipping over axis, possible values: both, horizontal, vertical and none
-                               allowFlip: 'both'
-                       }, options);
-                       
-                       if (!Array.isArray(options.pointerClassNames) || options.pointerClassNames.length !== (options.pointer ? 1 : 2)) options.pointerClassNames = [];
-                       if (['left', 'right', 'center'].indexOf(options.horizontal) === -1) options.horizontal = 'left';
-                       if (options.vertical !== 'bottom') options.vertical = 'top';
-                       if (['both', 'horizontal', 'vertical', 'none'].indexOf(options.allowFlip) === -1) options.allowFlip = 'both';
-                       
-                       // place element in the upper left corner to prevent calculation issues due to possible scrollbars
-                       DomUtil.setStyles(el, {
-                               bottom: 'auto !important',
-                               left: '0 !important',
-                               right: 'auto !important',
-                               top: '0 !important',
-                               visibility: 'hidden !important'
-                       });
-                       
-                       var elDimensions = DomUtil.outerDimensions(el);
-                       var refDimensions = DomUtil.outerDimensions((options.refDimensionsElement instanceof Element ? options.refDimensionsElement : ref));
-                       var refOffsets = DomUtil.offset(ref);
-                       var windowHeight = window.innerHeight;
-                       var windowWidth = document.body.clientWidth;
-                       
-                       var horizontal = { result: null };
-                       var alignCenter = false;
-                       if (options.horizontal === 'center') {
-                               alignCenter = true;
-                               horizontal = this._tryAlignmentHorizontal(options.horizontal, elDimensions, refDimensions, refOffsets, windowWidth);
-                               
-                               if (!horizontal.result) {
-                                       if (options.allowFlip === 'both' || options.allowFlip === 'horizontal') {
-                                               options.horizontal = 'left';
-                                       }
-                                       else {
-                                               horizontal.result = true;
-                                       }
-                               }
-                       }
-                       
-                       // in rtl languages we simply swap the value for 'horizontal'
-                       if (Language.get('wcf.global.pageDirection') === 'rtl') {
-                               options.horizontal = (options.horizontal === 'left') ? 'right' : 'left';
-                       }
-                       
-                       if (!horizontal.result) {
-                               var horizontalCenter = horizontal;
-                               horizontal = this._tryAlignmentHorizontal(options.horizontal, elDimensions, refDimensions, refOffsets, windowWidth);
-                               if (!horizontal.result && (options.allowFlip === 'both' || options.allowFlip === 'horizontal')) {
-                                       var horizontalFlipped = this._tryAlignmentHorizontal((options.horizontal === 'left' ? 'right' : 'left'), elDimensions, refDimensions, refOffsets, windowWidth);
-                                       // only use these results if it fits into the boundaries, otherwise both directions exceed and we honor the demanded direction
-                                       if (horizontalFlipped.result) {
-                                               horizontal = horizontalFlipped;
-                                       }
-                                       else if (alignCenter) {
-                                               horizontal = horizontalCenter;
-                                       }
-                               }
-                       }
-                       
-                       var left = horizontal.left;
-                       var right = horizontal.right;
-                       
-                       var vertical = this._tryAlignmentVertical(options.vertical, elDimensions, refDimensions, refOffsets, windowHeight, options.verticalOffset);
-                       if (!vertical.result && (options.allowFlip === 'both' || options.allowFlip === 'vertical')) {
-                               var verticalFlipped = this._tryAlignmentVertical((options.vertical === 'top' ? 'bottom' : 'top'), elDimensions, refDimensions, refOffsets, windowHeight, options.verticalOffset);
-                               // only use these results if it fits into the boundaries, otherwise both directions exceed and we honor the demanded direction
-                               if (verticalFlipped.result) {
-                                       vertical = verticalFlipped;
-                               }
-                       }
-                       
-                       var bottom = vertical.bottom;
-                       var top = vertical.top;
-                       
-                       // set pointer position
-                       if (options.pointer) {
-                               var pointer = DomTraverse.childrenByClass(el, 'elementPointer');
-                               pointer = pointer[0] || null;
-                               if (pointer === null) {
-                                       throw new Error("Expected the .elementPointer element to be a direct children.");
-                               }
-                               
-                               if (horizontal.align === 'center') {
-                                       pointer.classList.add('center');
-                                       
-                                       pointer.classList.remove('left');
-                                       pointer.classList.remove('right');
-                               }
-                               else {
-                                       pointer.classList.add(horizontal.align);
-                                       
-                                       pointer.classList.remove('center');
-                                       pointer.classList.remove(horizontal.align === 'left' ? 'right' : 'left');
-                               }
-                               
-                               if (vertical.align === 'top') {
-                                       pointer.classList.add('flipVertical');
-                               }
-                               else {
-                                       pointer.classList.remove('flipVertical');
-                               }
-                       }
-                       else if (options.pointerClassNames.length === 2) {
-                               var pointerBottom = 0;
-                               var pointerRight = 1;
-                               
-                               el.classList[(top === 'auto' ? 'add' : 'remove')](options.pointerClassNames[pointerBottom]);
-                               el.classList[(left === 'auto' ? 'add' : 'remove')](options.pointerClassNames[pointerRight]);
-                       }
-                       
-                       if (bottom !== 'auto') bottom = Math.round(bottom) + 'px';
-                       if (left !== 'auto') left = Math.ceil(left) + 'px';
-                       if (right !== 'auto') right = Math.floor(right) + 'px';
-                       if (top !== 'auto') top = Math.round(top) + 'px';
-                       
-                       DomUtil.setStyles(el, {
-                               bottom: bottom,
-                               left: left,
-                               right: right,
-                               top: top
-                       });
-                       
-                       elShow(el);
-                       el.style.removeProperty('visibility');
-               },
-               
-               /**
-                * Calculates left/right position and verifies if the element would be still within the page's boundaries.
-                * 
-                * @param       {string}                align           align to this side of the reference element
-                * @param       {Object<string, int>}   elDimensions    element dimensions
-                * @param       {Object<string, int>}   refDimensions   reference element dimensions
-                * @param       {Object<string, int>}   refOffsets      position of reference element relative to the document
-                * @param       {int}                   windowWidth     window width
-                * @returns     {Object<string, *>}     calculation results
-                */
-               _tryAlignmentHorizontal: function(align, elDimensions, refDimensions, refOffsets, windowWidth) {
-                       var left = 'auto';
-                       var right = 'auto';
-                       var result = true;
-                       
-                       if (align === 'left') {
-                               left = refOffsets.left;
-                               if (left + elDimensions.width > windowWidth) {
-                                       result = false;
-                               }
-                       }
-                       else if (align === 'right') {
-                               if (refOffsets.left + refDimensions.width < elDimensions.width) {
-                                       result = false;
-                               }
-                               else {
-                                       right = windowWidth - (refOffsets.left + refDimensions.width);
-                                       if (right < 0) {
-                                               result = false;
-                                       }
-                               }
-                       }
-                       else {
-                               left = refOffsets.left + (refDimensions.width / 2) - (elDimensions.width / 2);
-                               left = ~~left;
-                               
-                               if (left < 0 || left + elDimensions.width > windowWidth) {
-                                       result = false;
-                               }
-                       }
-                       
-                       return {
-                               align: align,
-                               left: left,
-                               right: right,
-                               result: result
-                       };
-               },
-               
-               /**
-                * Calculates top/bottom position and verifies if the element would be still within the page's boundaries.
-                * 
-                * @param       {string}                align           align to this side of the reference element
-                * @param       {Object<string, int>}   elDimensions    element dimensions
-                * @param       {Object<string, int>}   refDimensions   reference element dimensions
-                * @param       {Object<string, int>}   refOffsets      position of reference element relative to the document
-                * @param       {int}                   windowHeight    window height
-                * @param       {int}                   verticalOffset  desired gap between element and reference element
-                * @returns     {object<string, *>}     calculation results
-                */
-               _tryAlignmentVertical: function(align, elDimensions, refDimensions, refOffsets, windowHeight, verticalOffset) {
-                       var bottom = 'auto';
-                       var top = 'auto';
-                       var result = true;
-                       
-                       if (align === 'top') {
-                               var bodyHeight = document.body.clientHeight;
-                               bottom = (bodyHeight - refOffsets.top) + verticalOffset;
-                               if (bodyHeight - (bottom + elDimensions.height) < (window.scrollY || window.pageYOffset)) {
-                                       result = false;
-                               }
-                       }
-                       else {
-                               top = refOffsets.top + refDimensions.height + verticalOffset;
-                               if (top + elDimensions.height - (window.scrollY || window.pageYOffset) > windowHeight) {
-                                       result = false;
-                               }
-                       }
-                       
-                       return {
-                               align: align,
-                               bottom: bottom,
-                               top: top,
-                               result: result
-                       };
-               }
-       };
-});
-
-/**
- * Allows to be informed when a click event bubbled up to the document's body.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/CloseOverlay
- */
-define('WoltLabSuite/Core/Ui/CloseOverlay',['CallbackList'], function(CallbackList) {
-       "use strict";
-       
-       var _callbackList = new CallbackList();
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/CloseOverlay
-        */
-       var UiCloseOverlay = {
-               /**
-                * Sets up global event listener for bubbled clicks events.
-                */
-               setup: function() {
-                       document.body.addEventListener(WCF_CLICK_EVENT, this.execute.bind(this));
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/CallbackList#add
-                */
-               add: _callbackList.add.bind(_callbackList),
-               
-               /**
-                * @see WoltLabSuite/Core/CallbackList#remove
-                */
-               remove: _callbackList.remove.bind(_callbackList),
-               
-               /**
-                * Invokes all registered callbacks.
-                */
-               execute: function() {
-                       _callbackList.forEach(null, function(callback) {
-                               callback();
-                       });
-               }
-       };
-       
-       UiCloseOverlay.setup();
-       
-       return UiCloseOverlay;
-});
-
-/**
- * Simple dropdown implementation.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Dropdown/Simple
- */
-define(
-       'WoltLabSuite/Core/Ui/Dropdown/Simple',[       'CallbackList', 'Core', 'Dictionary', 'EventKey', 'Ui/Alignment', 'Dom/ChangeListener', 'Dom/Traverse', 'Dom/Util', 'Ui/CloseOverlay'],
-       function(CallbackList,   Core,   Dictionary,   EventKey,   UiAlignment,    DomChangeListener,    DomTraverse,    DomUtil,    UiCloseOverlay)
-{
-       "use strict";
-       
-       var _availableDropdowns = null;
-       var _callbacks = new CallbackList();
-       var _didInit = false;
-       var _dropdowns = new Dictionary();
-       var _menus = new Dictionary();
-       var _menuContainer = null;
-       var _callbackDropdownMenuKeyDown =  null;
-       var _activeTargetId = '';
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Dropdown/Simple
-        */
-       return {
-               /**
-                * Performs initial setup such as setting up dropdowns and binding listeners.
-                */
-               setup: function() {
-                       if (_didInit) return;
-                       _didInit = true;
-                       
-                       _menuContainer = elCreate('div');
-                       _menuContainer.className = 'dropdownMenuContainer';
-                       document.body.appendChild(_menuContainer);
-                       
-                       _availableDropdowns = elByClass('dropdownToggle');
-                       
-                       this.initAll();
-                       
-                       UiCloseOverlay.add('WoltLabSuite/Core/Ui/Dropdown/Simple', this.closeAll.bind(this));
-                       DomChangeListener.add('WoltLabSuite/Core/Ui/Dropdown/Simple', this.initAll.bind(this));
-                       
-                       document.addEventListener('scroll', this._onScroll.bind(this));
-                       
-                       // expose on window object for backward compatibility
-                       window.bc_wcfSimpleDropdown = this;
-                       
-                       _callbackDropdownMenuKeyDown = this._dropdownMenuKeyDown.bind(this);
-               },
-               
-               /**
-                * Loops through all possible dropdowns and registers new ones.
-                */
-               initAll: function() {
-                       for (var i = 0, length = _availableDropdowns.length; i < length; i++) {
-                               this.init(_availableDropdowns[i], false);
-                       }
-               },
-               
-               /**
-                * Initializes a dropdown.
-                * 
-                * @param       {Element}       button
-                * @param       {boolean|Event} isLazyInitialization
-                */
-               init: function(button, isLazyInitialization) {
-                       this.setup();
-                       
-                       elAttr(button, 'role', 'button');
-                       elAttr(button, 'tabindex', '0');
-                       elAttr(button, 'aria-haspopup', true);
-                       elAttr(button, 'aria-expanded', false);
-                       
-                       if (button.classList.contains('jsDropdownEnabled') || elData(button, 'target')) {
-                               return false;
-                       }
-                       
-                       var dropdown = DomTraverse.parentByClass(button, 'dropdown');
-                       if (dropdown === null) {
-                               throw new Error("Invalid dropdown passed, button '" + DomUtil.identify(button) + "' does not have a parent with .dropdown.");
-                       }
-                       
-                       var menu = DomTraverse.nextByClass(button, 'dropdownMenu');
-                       if (menu === null) {
-                               throw new Error("Invalid dropdown passed, button '" + DomUtil.identify(button) + "' does not have a menu as next sibling.");
-                       }
-                       
-                       // move menu into global container
-                       _menuContainer.appendChild(menu);
-                       
-                       var containerId = DomUtil.identify(dropdown);
-                       if (!_dropdowns.has(containerId)) {
-                               button.classList.add('jsDropdownEnabled');
-                               button.addEventListener(WCF_CLICK_EVENT, this._toggle.bind(this));
-                               button.addEventListener('keydown', this._handleKeyDown.bind(this));
-                               
-                               _dropdowns.set(containerId, dropdown);
-                               _menus.set(containerId, menu);
-                               
-                               if (!containerId.match(/^wcf\d+$/)) {
-                                       elData(menu, 'source', containerId);
-                               }
-                               
-                               // prevent page scrolling
-                               if (menu.childElementCount && menu.children[0].classList.contains('scrollableDropdownMenu')) {
-                                       menu = menu.children[0];
-                                       elData(menu, 'scroll-to-active', true);
-                                       
-                                       var menuHeight = null, menuRealHeight = null;
-                                       menu.addEventListener('wheel', function (event) {
-                                               if (menuHeight === null) menuHeight = menu.clientHeight;
-                                               if (menuRealHeight === null) menuRealHeight = menu.scrollHeight;
-                                               
-                                               // negative value: scrolling up
-                                               if (event.deltaY < 0 && menu.scrollTop === 0) {
-                                                       event.preventDefault();
-                                               }
-                                               else if (event.deltaY > 0 && (menu.scrollTop + menuHeight === menuRealHeight)) {
-                                                       event.preventDefault();
-                                               }
-                                       }, { passive: false });
-                               }
-                       }
-                       
-                       elData(button, 'target', containerId);
-                       
-                       if (isLazyInitialization) {
-                               setTimeout(function() {
-                                       elData(button, 'dropdown-lazy-init', (isLazyInitialization instanceof MouseEvent));
-                                       
-                                       Core.triggerEvent(button, WCF_CLICK_EVENT);
-                                       
-                                       setTimeout(function() {
-                                               button.removeAttribute('data-dropdown-lazy-init');
-                                       }, 10);
-                               }, 10);
-                       }
-               },
-               
-               /**
-                * Initializes a remote-controlled dropdown.
-                * 
-                * @param       {Element}       dropdown        dropdown wrapper element
-                * @param       {Element}       menu            menu list element
-                */
-               initFragment: function(dropdown, menu) {
-                       this.setup();
-                       
-                       var containerId = DomUtil.identify(dropdown);
-                       if (_dropdowns.has(containerId)) {
-                               return;
-                       }
-                       
-                       _dropdowns.set(containerId, dropdown);
-                       _menuContainer.appendChild(menu);
-                       
-                       _menus.set(containerId, menu);
-               },
-               
-               /**
-                * Registers a callback for open/close events.
-                * 
-                * @param       {string}                        containerId     dropdown wrapper id
-                * @param       {function(string, string)}      callback
-                */
-               registerCallback: function(containerId, callback) {
-                       _callbacks.add(containerId, callback);
-               },
-               
-               /**
-                * Returns the requested dropdown wrapper element.
-                * 
-                * @return      {Element}       dropdown wrapper element
-                */
-               getDropdown: function(containerId) {
-                       return _dropdowns.get(containerId);
-               },
-               
-               /**
-                * Returns the requested dropdown menu list element.
-                * 
-                * @return      {Element}       menu list element
-                */
-               getDropdownMenu: function(containerId) {
-                       return _menus.get(containerId);
-               },
-               
-               /**
-                * Toggles the requested dropdown between opened and closed.
-                * 
-                * @param       {string}        containerId             dropdown wrapper id
-                * @param       {Element=}      referenceElement        alternative reference element, used for reusable dropdown menus
-                * @param       {boolean=}      disableAutoFocus
-                */
-               toggleDropdown: function(containerId, referenceElement, disableAutoFocus) {
-                       this._toggle(null, containerId, referenceElement, disableAutoFocus);
-               },
-               
-               /**
-                * Calculates and sets the alignment of given dropdown.
-                * 
-                * @param       {Element}       dropdown                dropdown wrapper element
-                * @param       {Element}       dropdownMenu            menu list element
-                * @param       {Element=}      alternateElement        alternative reference element for alignment
-                */
-               setAlignment: function(dropdown, dropdownMenu, alternateElement) {
-                       // check if button belongs to an i18n textarea
-                       var button = elBySel('.dropdownToggle', dropdown), refDimensionsElement;
-                       if (button !== null && button.parentNode.classList.contains('inputAddonTextarea')) {
-                               refDimensionsElement = button;
-                       }
-                       
-                       UiAlignment.set(dropdownMenu, alternateElement || dropdown, {
-                               pointerClassNames: ['dropdownArrowBottom', 'dropdownArrowRight'],
-                               refDimensionsElement: refDimensionsElement || null,
-                               
-                               // alignment
-                               horizontal: (elData(dropdownMenu, 'dropdown-alignment-horizontal') === 'right') ? 'right' : 'left',
-                               vertical: (elData(dropdownMenu, 'dropdown-alignment-vertical') === 'top') ? 'top' : 'bottom',
-                               
-                               allowFlip: elData(dropdownMenu, 'dropdown-allow-flip') || 'both'
-                       });
-               },
-               
-               /**
-                * Calculates and sets the alignment of the dropdown identified by given id.
-                * 
-                * @param       {string}        containerId     dropdown wrapper id
-                */
-               setAlignmentById: function(containerId) {
-                       var dropdown = _dropdowns.get(containerId);
-                       if (dropdown === undefined) {
-                               throw new Error("Unknown dropdown identifier '" + containerId + "'.");
-                       }
-                       
-                       var menu = _menus.get(containerId);
-                       
-                       this.setAlignment(dropdown, menu);
-               },
-               
-               /**
-                * Returns true if target dropdown exists and is open.
-                * 
-                * @param       {string}        containerId     dropdown wrapper id
-                * @return      {boolean}       true if dropdown exists and is open
-                */
-               isOpen: function(containerId) {
-                       var menu = _menus.get(containerId);
-                       return (menu !== undefined && menu.classList.contains('dropdownOpen'));
-               },
-               
-               /**
-                * Opens the dropdown unless it is already open.
-                * 
-                * @param       {string}        containerId     dropdown wrapper id
-                * @param       {boolean=}      disableAutoFocus
-                */
-               open: function(containerId, disableAutoFocus) {
-                       var menu = _menus.get(containerId);
-                       if (menu !== undefined && !menu.classList.contains('dropdownOpen')) {
-                               this.toggleDropdown(containerId, undefined, disableAutoFocus);
-                       }
-               },
-               
-               /**
-                * Closes the dropdown identified by given id without notifying callbacks.
-                * 
-                * @param       {string}        containerId     dropdown wrapper id
-                */
-               close: function(containerId) {
-                       var dropdown = _dropdowns.get(containerId);
-                       if (dropdown !== undefined) {
-                               dropdown.classList.remove('dropdownOpen');
-                               _menus.get(containerId).classList.remove('dropdownOpen');
-                       }
-               },
-               
-               /**
-                * Closes all dropdowns.
-                */
-               closeAll: function() {
-                       _dropdowns.forEach((function(dropdown, containerId) {
-                               if (dropdown.classList.contains('dropdownOpen')) {
-                                       dropdown.classList.remove('dropdownOpen');
-                                       _menus.get(containerId).classList.remove('dropdownOpen');
-                                       
-                                       this._notifyCallbacks(containerId, 'close');
-                               }
-                       }).bind(this));
-               },
-               
-               /**
-                * Destroys a dropdown identified by given id.
-                * 
-                * @param       {string}        containerId     dropdown wrapper id
-                * @return      {boolean}       false for unknown dropdowns
-                */
-               destroy: function(containerId) {
-                       if (!_dropdowns.has(containerId)) {
-                               return false;
-                       }
-                       
-                       try {
-                               this.close(containerId);
-                               
-                               elRemove(_menus.get(containerId));
-                       }
-                       catch (e) {
-                               // the elements might not exist anymore thus ignore all errors while cleaning up
-                       }
-                       
-                       _menus.delete(containerId);
-                       _dropdowns.delete(containerId);
-                       
-                       return true;
-               },
-               
-               /**
-                * Handles dropdown positions in overlays when scrolling in the overlay.
-                * 
-                * @param       {Event}         event   event object
-                */
-               _onDialogScroll: function(event) {
-                       var dialogContent = event.currentTarget;
-                       //noinspection JSCheckFunctionSignatures
-                       var dropdowns = elBySelAll('.dropdown.dropdownOpen', dialogContent);
-                       
-                       for (var i = 0, length = dropdowns.length; i < length; i++) {
-                               var dropdown = dropdowns[i];
-                               var containerId = DomUtil.identify(dropdown);
-                               var offset = DomUtil.offset(dropdown);
-                               var dialogOffset = DomUtil.offset(dialogContent);
-                               
-                               // check if dropdown toggle is still (partially) visible
-                               if (offset.top + dropdown.clientHeight <= dialogOffset.top) {
-                                       // top check
-                                       this.toggleDropdown(containerId);
-                               }
-                               else if (offset.top >= dialogOffset.top + dialogContent.offsetHeight) {
-                                       // bottom check
-                                       this.toggleDropdown(containerId);
-                               }
-                               else if (offset.left <= dialogOffset.left) {
-                                       // left check
-                                       this.toggleDropdown(containerId);
-                               }
-                               else if (offset.left >= dialogOffset.left + dialogContent.offsetWidth) {
-                                       // right check
-                                       this.toggleDropdown(containerId);
-                               }
-                               else {
-                                       this.setAlignment(_dropdowns.get(containerId), _menus.get(containerId));
-                               }
-                       }
-               },
-               
-               /**
-                * Recalculates dropdown positions on page scroll.
-                */
-               _onScroll: function() {
-                       _dropdowns.forEach((function(dropdown, containerId) {
-                               if (dropdown.classList.contains('dropdownOpen')) {
-                                       if (elDataBool(dropdown, 'is-overlay-dropdown-button')) {
-                                               this.setAlignment(dropdown, _menus.get(containerId));
-                                       }
-                                       else {
-                                               var menu = _menus.get(dropdown.id);
-                                               if (!elDataBool(menu, 'dropdown-ignore-page-scroll')) {
-                                                       this.close(containerId);
-                                               }
-                                       }
-                               }
-                       }).bind(this));
-               },
-               
-               /**
-                * Notifies callbacks on status change.
-                * 
-                * @param       {string}        containerId     dropdown wrapper id
-                * @param       {string}        action          can be either 'open' or 'close'
-                */
-               _notifyCallbacks: function(containerId, action) {
-                       _callbacks.forEach(containerId, function(callback) {
-                               callback(containerId, action);
-                       });
-               },
-               
-               /**
-                * Toggles the dropdown's state between open and close.
-                * 
-                * @param       {?Event}        event                   event object, should be 'null' if targetId is given
-                * @param       {string?}       targetId                dropdown wrapper id
-                * @param       {Element=}      alternateElement        alternative reference element for alignment
-                * @param       {boolean=}      disableAutoFocus
-                * @return      {boolean}       'false' if event is not null
-                */
-               _toggle: function(event, targetId, alternateElement, disableAutoFocus) {
-                       if (event !== null) {
-                               event.preventDefault();
-                               event.stopPropagation();
-                               
-                               //noinspection JSCheckFunctionSignatures
-                               targetId = elData(event.currentTarget, 'target');
-                               
-                               if (disableAutoFocus === undefined && event instanceof MouseEvent) {
-                                       disableAutoFocus = true;
-                               }
-                       }
-                       
-                       var dropdown = _dropdowns.get(targetId), preventToggle = false;
-                       if (dropdown !== undefined) {
-                               var button;
-                               
-                               // check if the dropdown is still the same, as some components (e.g. page actions)
-                               // re-create the parent of a button
-                               if (event) {
-                                       button = event.currentTarget, parent = button.parentNode;
-                                       if (parent !== dropdown) {
-                                               parent.classList.add('dropdown');
-                                               parent.id = dropdown.id;
-                                               
-                                               // remove dropdown class and id from old parent
-                                               dropdown.classList.remove('dropdown');
-                                               dropdown.id = '';
-                                               
-                                               dropdown = parent;
-                                               _dropdowns.set(targetId, parent);
-                                       }
-                               }
-                               
-                               if (disableAutoFocus === undefined) {
-                                       button = dropdown.closest('.dropdownToggle');
-                                       if (!button) {
-                                               button = elBySel('.dropdownToggle', dropdown);
-                                               
-                                               if (!button && dropdown.id) {
-                                                       button = elBySel('[data-target="' + dropdown.id + '"]');
-                                               }
-                                       }
-                                       
-                                       if (button && elDataBool(button, 'dropdown-lazy-init')) {
-                                               disableAutoFocus = true;
-                                       }
-                               }
-                               
-                               // Repeated clicks on the dropdown button will not cause it to close, the only way
-                               // to close it is by clicking somewhere else in the document or on another dropdown
-                               // toggle. This is used with the search bar to prevent the dropdown from closing by
-                               // setting the caret position in the search input field.
-                               if (elDataBool(dropdown, 'dropdown-prevent-toggle') && dropdown.classList.contains('dropdownOpen')) {
-                                       preventToggle = true;
-                               }
-                               
-                               // check if 'isOverlayDropdownButton' is set which indicates that the dropdown toggle is within an overlay
-                               if (elData(dropdown, 'is-overlay-dropdown-button') === '') {
-                                       var dialogContent = DomTraverse.parentByClass(dropdown, 'dialogContent');
-                                       elData(dropdown, 'is-overlay-dropdown-button', (dialogContent !== null));
-                                       
-                                       if (dialogContent !== null) {
-                                               dialogContent.addEventListener('scroll', this._onDialogScroll.bind(this));
-                                       }
-                               }
-                       }
-                       
-                       // close all dropdowns
-                       _activeTargetId = '';
-                       _dropdowns.forEach((function(dropdown, containerId) {
-                               var menu = _menus.get(containerId);
-                               
-                               if (dropdown.classList.contains('dropdownOpen')) {
-                                       if (preventToggle === false) {
-                                               dropdown.classList.remove('dropdownOpen');
-                                               menu.classList.remove('dropdownOpen');
-                                               
-                                               var button = elBySel('.dropdownToggle', dropdown);
-                                               if (button) elAttr(button, 'aria-expanded', false);
-                                               
-                                               this._notifyCallbacks(containerId, 'close');
-                                       }
-                                       else {
-                                               _activeTargetId = targetId;
-                                       }
-                               }
-                               else if (containerId === targetId && menu.childElementCount > 0) {
-                                       _activeTargetId = targetId;
-                                       dropdown.classList.add('dropdownOpen');
-                                       menu.classList.add('dropdownOpen');
-                                       
-                                       var button = elBySel('.dropdownToggle', dropdown);
-                                       if (button) elAttr(button, 'aria-expanded', true);
-                                       
-                                       if (menu.childElementCount && elDataBool(menu.children[0], 'scroll-to-active')) {
-                                               var list = menu.children[0];
-                                               list.removeAttribute('data-scroll-to-active');
-                                               
-                                               var active = null;
-                                               for (var i = 0, length = list.childElementCount; i < length; i++) {
-                                                       if (list.children[i].classList.contains('active')) {
-                                                               active = list.children[i];
-                                                               break;
-                                                       }
-                                               }
-                                               
-                                               if (active) {
-                                                       list.scrollTop = Math.max((active.offsetTop + active.clientHeight) - menu.clientHeight, 0);
-                                               }
-                                       }
-                                       
-                                       var itemList = elBySel('.scrollableDropdownMenu', menu);
-                                       if (itemList !== null) {
-                                               itemList.classList[(itemList.scrollHeight > itemList.clientHeight ? 'add' : 'remove')]('forceScrollbar');
-                                       }
-                                       
-                                       this._notifyCallbacks(containerId, 'open');
-                                       
-                                       var firstListItem = null;
-                                       if (!disableAutoFocus) {
-                                               elAttr(menu, 'role', 'menu');
-                                               elAttr(menu, 'tabindex', -1);
-                                               menu.removeEventListener('keydown', _callbackDropdownMenuKeyDown);
-                                               menu.addEventListener('keydown', _callbackDropdownMenuKeyDown);
-                                               elBySelAll('li', menu, function (listItem) {
-                                                       if (!listItem.clientHeight) return;
-                                                       if (firstListItem === null) firstListItem = listItem;
-                                                       else if (listItem.classList.contains('active')) firstListItem = listItem;
-                                                       
-                                                       elAttr(listItem, 'role', 'menuitem');
-                                                       elAttr(listItem, 'tabindex', -1);
-                                               });
-                                       }
-                                       
-                                       this.setAlignment(dropdown, menu, alternateElement);
-                                       
-                                       if (firstListItem !== null) {
-                                               firstListItem.focus();
-                                       }
-                               }
-                       }).bind(this));
-                       
-                       //noinspection JSDeprecatedSymbols
-                       window.WCF.Dropdown.Interactive.Handler.closeAll();
-                       
-                       return (event === null);
-               },
-               
-               _handleKeyDown: function(event) {
-                       if (EventKey.Enter(event) || EventKey.Space(event)) {
-                               event.preventDefault();
-                               this._toggle(event);
-                       }
-               },
-               
-               _dropdownMenuKeyDown: function(event) {
-                       var button, dropdown;
-                       
-                       var activeItem = document.activeElement;
-                       if (activeItem.nodeName !== 'LI') {
-                               return;
-                       }
-                       
-                       if (EventKey.ArrowDown(event) || EventKey.ArrowUp(event) || EventKey.End(event) || EventKey.Home(event)) {
-                               event.preventDefault();
-                               
-                               var listItems = Array.prototype.slice.call(elBySelAll('li', activeItem.closest('.dropdownMenu')));
-                               if (EventKey.ArrowUp(event) || EventKey.End(event)) {
-                                       listItems.reverse();
-                               }
-                               var newActiveItem = null;
-                               var isValidItem = function(listItem) {
-                                       return !listItem.classList.contains('dropdownDivider') && listItem.clientHeight > 0;
-                               };
-                               
-                               var activeIndex = listItems.indexOf(activeItem);
-                               if (EventKey.End(event) || EventKey.Home(event)) {
-                                       activeIndex = -1;
-                               }
-                               
-                               for (var i = activeIndex + 1; i < listItems.length; i++) {
-                                       if (isValidItem(listItems[i])) {
-                                               newActiveItem = listItems[i];
-                                               break;
-                                       }
-                               }
-                               
-                               if (newActiveItem === null) {
-                                       for (i = 0; i < listItems.length; i++) {
-                                               if (isValidItem(listItems[i])) {
-                                                       newActiveItem = listItems[i];
-                                                       break;
-                                               }
-                                       }
-                               }
-                               
-                               newActiveItem.focus();
-                       }
-                       else if (EventKey.Enter(event) || EventKey.Space(event)) {
-                               event.preventDefault();
-                               
-                               var target = activeItem;
-                               if (target.childElementCount === 1 && (target.children[0].nodeName === 'SPAN' || target.children[0].nodeName === 'A')) {
-                                       target = target.children[0];
-                               }
-                               
-                               dropdown = _dropdowns.get(_activeTargetId);
-                               button = elBySel('.dropdownToggle', dropdown);
-                               require(['Core'], function(Core) {
-                                       var mouseEvent = elData(dropdown, 'a11y-mouse-event') || 'click';
-                                       Core.triggerEvent(target, mouseEvent);
-                                       
-                                       if (button) button.focus();
-                               });
-                       }
-                       else if (EventKey.Escape(event) || EventKey.Tab(event)) {
-                               event.preventDefault();
-                               
-                               dropdown = _dropdowns.get(_activeTargetId);
-                               button = elBySel('.dropdownToggle', dropdown);
-                               // Remote controlled drop-down menus may not have a dedicated toggle button, instead the
-                               // `dropdown` element itself is the button.
-                               if (button === null && !dropdown.classList.contains('dropdown')) {
-                                       button = dropdown;
-                               }
-                               
-                               this._toggle(null, _activeTargetId);
-                               if (button) button.focus();
-                       }
-               }
-       };
-});
-
-/**
- * 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     WoltLabSuite/Core/Devtools
- */
-define('WoltLabSuite/Core/Devtools',[], 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;
-});
-
-/**
- * Versatile event system similar to the WCF-PHP counter part.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Event/Handler
- */
-define('WoltLabSuite/Core/Event/Handler',['Core', 'Devtools', 'Dictionary'], function(Core, Devtools, Dictionary) {
-       "use strict";
-       
-       var _listeners = new Dictionary();
-       
-       /**
-        * @exports     WoltLabSuite/Core/Event/Handler
-        */
-       return {
-               /**
-                * Adds an event listener.
-                * 
-                * @param       {string}                identifier      event identifier
-                * @param       {string}                action          action name
-                * @param       {function(object)}      callback        callback function
-                * @return      {string}        uuid required for listener removal
-                */
-               add: function(identifier, action, callback) {
-                       if (typeof callback !== 'function') {
-                               throw new TypeError("[WoltLabSuite/Core/Event/Handler] Expected a valid callback for '" + action + "@" + identifier + "'.");
-                       }
-                       
-                       var actions = _listeners.get(identifier);
-                       if (actions === undefined) {
-                               actions = new Dictionary();
-                               _listeners.set(identifier, actions);
-                       }
-                       
-                       var callbacks = actions.get(action);
-                       if (callbacks === undefined) {
-                               callbacks = new Dictionary();
-                               actions.set(action, callbacks);
-                       }
-                       
-                       var uuid = Core.getUuid();
-                       callbacks.set(uuid, callback);
-                       
-                       return uuid;
-               },
-               
-               /**
-                * Fires an event and notifies all listeners.
-                * 
-                * @param       {string}        identifier      event identifier
-                * @param       {string}        action          action name
-                * @param       {object=}       data            event data
-                */
-               fire: function(identifier, action, data) {
-                       Devtools._internal_.eventLog(identifier, action);
-                       
-                       data = data || {};
-                       
-                       var actions = _listeners.get(identifier);
-                       if (actions !== undefined) {
-                               var callbacks = actions.get(action);
-                               if (callbacks !== undefined) {
-                                       callbacks.forEach(function(callback) {
-                                               callback(data);
-                                       });
-                               }
-                       }
-               },
-               
-               /**
-                * Removes an event listener, requires the uuid returned by add().
-                * 
-                * @param       {string}        identifier      event identifier
-                * @param       {string}        action          action name
-                * @param       {string}        uuid            listener uuid
-                */
-               remove: function(identifier, action, uuid) {
-                       var actions = _listeners.get(identifier);
-                       if (actions === undefined) {
-                               return;
-                       }
-                       
-                       var callbacks = actions.get(action);
-                       if (callbacks === undefined) {
-                               return;
-                       }
-                       
-                       callbacks['delete'](uuid);
-               },
-               
-               /**
-                * Removes all event listeners for given action. Omitting the second parameter will
-                * remove all listeners for this identifier.
-                * 
-                * @param       {string}        identifier      event identifier
-                * @param       {string=}       action          action name
-                */
-               removeAll: function(identifier, action) {
-                       if (typeof action !== 'string') action = undefined;
-                       
-                       var actions = _listeners.get(identifier);
-                       if (actions === undefined) {
-                               return;
-                       }
-                       
-                       if (typeof action === 'undefined') {
-                               _listeners['delete'](identifier);
-                       }
-                       else {
-                               actions['delete'](action);
-                       }
-               },
-               
-               /**
-                * Removes all listeners registered for an identifier and ending with a special suffix.
-                * This is commonly used to unbound event handlers for the editor.
-                * 
-                * @param       {string}        identifier      event identifier
-                * @param       {string}        suffix          action suffix
-                */
-               removeAllBySuffix: function (identifier, suffix) {
-                       var actions = _listeners.get(identifier);
-                       if (actions === undefined) {
-                               return;
-                       }
-                       
-                       suffix = '_' + suffix;
-                       var length = suffix.length * -1;
-                       actions.forEach((function (callbacks, action) {
-                               //noinspection JSUnresolvedFunction
-                               if (action.substr(length) === suffix) {
-                                       this.removeAll(identifier, action);
-                               }
-                       }).bind(this));
-               }
-       };
-});
-
-/**
- * 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     WoltLabSuite/Core/List
- */
-define('WoltLabSuite/Core/List',[], 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;
-});
-
-/**
- * Modal dialog handler.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Dialog
- */
-define(
-       'WoltLabSuite/Core/Ui/Dialog',[
-               'Ajax',         'Core',       'Dictionary',
-               'Environment',  'Language',   'ObjectMap', 'Dom/ChangeListener',
-               'Dom/Traverse', 'Dom/Util',   'Ui/Confirmation', 'Ui/Screen', 'Ui/SimpleDropdown',
-               'EventHandler', 'List',       'EventKey'
-       ],
-       function(
-               Ajax,           Core,         Dictionary,
-               Environment,    Language,     ObjectMap,   DomChangeListener,
-               DomTraverse,    DomUtil,      UiConfirmation, UiScreen, UiSimpleDropdown,
-               EventHandler,   List,         EventKey
-       )
-{
-       "use strict";
-       
-       var _activeDialog = null;
-       var _callbackFocus = null;
-       var _container = null;
-       var _dialogs = new Dictionary();
-       var _dialogFullHeight = false;
-       var _dialogObjects = new ObjectMap();
-       var _dialogToObject = new Dictionary();
-       var _focusedBeforeDialog = null;
-       var _keyupListener = null;
-       var _staticDialogs = elByClass('jsStaticDialog');
-       var _validCallbacks = ['onBeforeClose', 'onClose', 'onShow'];
-       
-       // list of supported `input[type]` values for dialog submit
-       var _validInputTypes = ['number', 'password', 'search', 'tel', 'text', 'url'];
-       
-       var _focusableElements = [
-               'a[href]:not([tabindex^="-"]):not([inert])',
-               'area[href]:not([tabindex^="-"]):not([inert])',
-               'input:not([disabled]):not([inert])',
-               'select:not([disabled]):not([inert])',
-               'textarea:not([disabled]):not([inert])',
-               'button:not([disabled]):not([inert])',
-               'iframe:not([tabindex^="-"]):not([inert])',
-               'audio:not([tabindex^="-"]):not([inert])',
-               'video:not([tabindex^="-"]):not([inert])',
-               '[contenteditable]:not([tabindex^="-"]):not([inert])',
-               '[tabindex]:not([tabindex^="-"]):not([inert])'
-       ];
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Dialog
-        */
-       return {
-               /**
-                * Sets up global container and internal variables.
-                */
-               setup: function() {
-                       // Fetch Ajax, as it cannot be provided because of a circular dependency
-                       if (Ajax === undefined) Ajax = require('Ajax');
-                       
-                       _container = elCreate('div');
-                       _container.classList.add('dialogOverlay');
-                       elAttr(_container, 'aria-hidden', 'true');
-                       _container.addEventListener('mousedown', this._closeOnBackdrop.bind(this));
-                       _container.addEventListener('wheel', function (event) {
-                               if (event.target === _container) {
-                                       event.preventDefault();
-                               }
-                       }, { passive: false });
-                       
-                       elById('content').appendChild(_container);
-                       
-                       _keyupListener = (function(event) {
-                               if (event.keyCode === 27) {
-                                       if (event.target.nodeName !== 'INPUT' && event.target.nodeName !== 'TEXTAREA') {
-                                               this.close(_activeDialog);
-                                               
-                                               return false;
-                                       }
-                               }
-                               
-                               return true;
-                       }).bind(this);
-                       
-                       UiScreen.on('screen-xs', {
-                               match: function() { _dialogFullHeight = true; },
-                               unmatch: function() { _dialogFullHeight = false; },
-                               setup: function() { _dialogFullHeight = true; }
-                       });
-                       
-                       this._initStaticDialogs();
-                       DomChangeListener.add('Ui/Dialog', this._initStaticDialogs.bind(this));
-                       
-                       UiScreen.setDialogContainer(_container);
-                       
-                       // mobile safari dynamically shows/hides the bottom browser bar
-                       // causing the window height to differ significantly
-                       if (Environment.platform() === 'ios') {
-                               window.addEventListener('resize', (function () {
-                                       _dialogs.forEach((function (dialog) {
-                                               if (!elAttrBool(dialog.dialog, 'aria-hidden')) {
-                                                       this.rebuild(elData(dialog.dialog, 'id'));
-                                               }
-                                       }).bind(this));
-                               }).bind(this));
-                       }
-               },
-               
-               _initStaticDialogs: function() {
-                       var button, container, id;
-                       while (_staticDialogs.length) {
-                               button = _staticDialogs[0];
-                               button.classList.remove('jsStaticDialog');
-                               
-                               id = elData(button, 'dialog-id');
-                               if (id && (container = elById(id))) {
-                                       ((function(button, container) {
-                                               container.classList.remove('jsStaticDialogContent');
-                                               elData(container, 'is-static-dialog', true);
-                                               elHide(container);
-                                               button.addEventListener(WCF_CLICK_EVENT, (function(event) {
-                                                       event.preventDefault();
-                                                       
-                                                       this.openStatic(container.id, null, { title: elData(container, 'title') });
-                                               }).bind(this));
-                                       }).bind(this))(button, container);
-                               }
-                       }
-               },
-               
-               /**
-                * Opens the dialog and implicitly creates it on first usage.
-                * 
-                * @param       {object}                        callbackObject  used to invoke `_dialogSetup()` on first call
-                * @param       {(string|DocumentFragment=}     html            html content or document fragment to use for dialog content
-                * @returns     {object<string, *>}             dialog data
-                */
-               open: function(callbackObject, html) {
-                       var dialogData = _dialogObjects.get(callbackObject);
-                       if (Core.isPlainObject(dialogData)) {
-                               // dialog already exists
-                               return this.openStatic(dialogData.id, html);
-                       }
-                       
-                       // initialize a new dialog
-                       if (typeof callbackObject._dialogSetup !== 'function') {
-                               throw new Error("Callback object does not implement the method '_dialogSetup()'.");
-                       }
-                       
-                       var setupData = callbackObject._dialogSetup();
-                       if (!Core.isPlainObject(setupData)) {
-                               throw new Error("Expected an object literal as return value of '_dialogSetup()'.");
-                       }
-                       
-                       dialogData = { id: setupData.id };
-                       
-                       var createOnly = true;
-                       if (setupData.source === undefined) {
-                               var dialogElement = elById(setupData.id);
-                               if (dialogElement === null) {
-                                       throw new Error("Element id '" + setupData.id + "' is invalid and no source attribute was given. If you want to use the `html` argument instead, please add `source: null` to your dialog configuration.");
-                               }
-                               
-                               setupData.source = document.createDocumentFragment();
-                               setupData.source.appendChild(dialogElement);
-                               
-                               // remove id and `display: none` from dialog element
-                               dialogElement.removeAttribute('id');
-                               elShow(dialogElement);
-                       }
-                       else if (setupData.source === null) {
-                               // `null` means there is no static markup and `html` should be used instead
-                               setupData.source = html;
-                       }
-                       
-                       else if (typeof setupData.source === 'function') {
-                               setupData.source();
-                       }
-                       else if (Core.isPlainObject(setupData.source)) {
-                               if (typeof html === 'string' && html.trim() !== '') {
-                                       setupData.source = html;
-                               }
-                               else {
-                                       Ajax.api(this, setupData.source.data, (function (data) {
-                                               if (data.returnValues && typeof data.returnValues.template === 'string') {
-                                                       this.open(callbackObject, data.returnValues.template);
-                                                       
-                                                       if (typeof setupData.source.after === 'function') {
-                                                               setupData.source.after(_dialogs.get(setupData.id).content, data);
-                                                       }
-                                               }
-                                       }).bind(this));
-                                       
-                                       // deferred initialization
-                                       return {};
-                               }
-                       }
-                       else {
-                               if (typeof setupData.source === 'string') {
-                                       var dialogElement = elCreate('div');
-                                       elAttr(dialogElement, 'id', setupData.id);
-                                       DomUtil.setInnerHtml(dialogElement, setupData.source);
-                                       
-                                       setupData.source = document.createDocumentFragment();
-                                       setupData.source.appendChild(dialogElement);
-                               }
-                               
-                               if (!setupData.source.nodeType || setupData.source.nodeType !== Node.DOCUMENT_FRAGMENT_NODE) {
-                                       throw new Error("Expected at least a document fragment as 'source' attribute.");
-                               }
-                               
-                               createOnly = false;
-                       }
-                       
-                       _dialogObjects.set(callbackObject, dialogData);
-                       _dialogToObject.set(setupData.id, callbackObject);
-                       
-                       return this.openStatic(setupData.id, setupData.source, setupData.options, createOnly);
-               },
-               
-               /**
-                * Opens an dialog, if the dialog is already open the content container
-                * will be replaced by the HTML string contained in the parameter html.
-                * 
-                * If id is an existing element id, html will be ignored and the referenced
-                * element will be appended to the content element instead.
-                * 
-                * @param       {string}                        id              element id, if exists the html parameter is ignored in favor of the existing element
-                * @param       {?(string|DocumentFragment)}    html            content html
-                * @param       {object<string, *>}             options         list of options, is completely ignored if the dialog already exists
-                * @param       {boolean=}                      createOnly      create the dialog but do not open it
-                * @return      {object<string, *>}             dialog data
-                */
-               openStatic: function(id, html, options, createOnly) {
-                       UiScreen.pageOverlayOpen();
-                       
-                       if (Environment.platform() !== 'desktop') {
-                               if (!this.isOpen(id)) {
-                                       UiScreen.scrollDisable();
-                               }
-                       }
-                       
-                       if (_dialogs.has(id)) {
-                               this._updateDialog(id, html);
-                       }
-                       else {
-                               options = Core.extend({
-                                       backdropCloseOnClick: true,
-                                       closable: true,
-                                       closeButtonLabel: Language.get('wcf.global.button.close'),
-                                       closeConfirmMessage: '',
-                                       disableContentPadding: false,
-                                       title: '',
-                                       
-                                       // callbacks
-                                       onBeforeClose: null,
-                                       onClose: null,
-                                       onShow: null
-                               }, options);
-                               
-                               if (!options.closable) options.backdropCloseOnClick = false;
-                               if (options.closeConfirmMessage) {
-                                       options.onBeforeClose = (function(id) {
-                                               UiConfirmation.show({
-                                                       confirm: this.close.bind(this, id),
-                                                       message: options.closeConfirmMessage
-                                               });
-                                       }).bind(this);
-                               }
-                               
-                               this._createDialog(id, html, options);
-                       }
-                       
-                       var data = _dialogs.get(id);
-                       
-                       // iOS breaks `position: fixed` when input elements or `contenteditable`
-                       // are focused, this will freeze the screen and force Safari to scroll
-                       // to the input field
-                       if (Environment.platform() === 'ios') {
-                               window.setTimeout((function () {
-                                       var input = elBySel('input, textarea', data.content);
-                                       if (input !== null) {
-                                               input.focus();
-                                       }
-                               }).bind(this), 200);
-                       }
-                       
-                       return data;
-               },
-               
-               /**
-                * Sets the dialog title.
-                * 
-                * @param       {(string|object)}       id              element id
-                * @param       {string}                title           dialog title
-                */
-               setTitle: function(id, title) {
-                       id = this._getDialogId(id);
-                       
-                       var data = _dialogs.get(id);
-                       if (data === undefined) {
-                               throw new Error("Expected a valid dialog id, '" + id + "' does not match any active dialog.");
-                       }
-                       
-                       var dialogTitle = elByClass('dialogTitle', data.dialog);
-                       if (dialogTitle.length) {
-                               dialogTitle[0].textContent = title;
-                       }
-               },
-               
-               /**
-                * Sets a callback function on runtime.
-                * 
-                * @param       {(string|object)}       id              element id
-                * @param       {string}                key             callback identifier
-                * @param       {?function}             value           callback function or `null`
-                */
-               setCallback: function(id, key, value) {
-                       if (typeof id === 'object') {
-                               var dialogData = _dialogObjects.get(id);
-                               if (dialogData !== undefined) {
-                                       id = dialogData.id;
-                               }
-                       }
-                       
-                       var data = _dialogs.get(id);
-                       if (data === undefined) {
-                               throw new Error("Expected a valid dialog id, '" + id + "' does not match any active dialog.");
-                       }
-                       
-                       if (_validCallbacks.indexOf(key) === -1) {
-                               throw new Error("Invalid callback identifier, '" + key + "' is not recognized.");
-                       }
-                       
-                       if (typeof value !== 'function' && value !== null) {
-                               throw new Error("Only functions or the 'null' value are acceptable callback values ('" + typeof value+ "' given).");
-                       }
-                       
-                       data[key] = value;
-               },
-               
-               /**
-                * Creates the DOM for a new dialog and opens it.
-                * 
-                * @param       {string}                        id              element id, if exists the html parameter is ignored in favor of the existing element
-                * @param       {?(string|DocumentFragment)}    html            content html
-                * @param       {object<string, *>}             options         list of options
-                * @param       {boolean=}                      createOnly      create the dialog but do not open it
-                */
-               _createDialog: function(id, html, options, createOnly) {
-                       var element = null;
-                       if (html === null) {
-                               element = elById(id);
-                               if (element === null) {
-                                       throw new Error("Expected either a HTML string or an existing element id.");
-                               }
-                       }
-                       
-                       var dialog = elCreate('div');
-                       dialog.classList.add('dialogContainer');
-                       elAttr(dialog, 'aria-hidden', 'true');
-                       elAttr(dialog, 'role', 'dialog');
-                       elData(dialog, 'id', id);
-                       
-                       var header = elCreate('header');
-                       dialog.appendChild(header);
-                       
-                       var titleId = DomUtil.getUniqueId();
-                       elAttr(dialog, 'aria-labelledby', titleId);
-                       
-                       var title = elCreate('span');
-                       title.classList.add('dialogTitle');
-                       title.textContent = options.title;
-                       elAttr(title, 'id', titleId);
-                       header.appendChild(title);
-                       
-                       if (options.closable) {
-                               var closeButton = elCreate('a');
-                               closeButton.className = 'dialogCloseButton jsTooltip';
-                               elAttr(closeButton, 'role', 'button');
-                               elAttr(closeButton, 'tabindex', '0');
-                               elAttr(closeButton, 'title', options.closeButtonLabel);
-                               elAttr(closeButton, 'aria-label', options.closeButtonLabel);
-                               closeButton.addEventListener(WCF_CLICK_EVENT, this._close.bind(this));
-                               header.appendChild(closeButton);
-                               
-                               var span = elCreate('span');
-                               span.className = 'icon icon24 fa-times';
-                               closeButton.appendChild(span);
-                       }
-                       
-                       var contentContainer = elCreate('div');
-                       contentContainer.classList.add('dialogContent');
-                       if (options.disableContentPadding) contentContainer.classList.add('dialogContentNoPadding');
-                       dialog.appendChild(contentContainer);
-                       
-                       contentContainer.addEventListener('wheel', function (event) {
-                               var allowScroll = false;
-                               var element = event.target, clientHeight, scrollHeight, scrollTop;
-                               while (true) {
-                                       clientHeight = element.clientHeight;
-                                       scrollHeight = element.scrollHeight;
-                                       
-                                       if (clientHeight < scrollHeight) {
-                                               scrollTop = element.scrollTop;
-                                               
-                                               // negative value: scrolling up
-                                               if (event.deltaY < 0 && scrollTop > 0) {
-                                                       allowScroll = true;
-                                                       break;
-                                               }
-                                               else if (event.deltaY > 0 && (scrollTop + clientHeight < scrollHeight)) {
-                                                       allowScroll = true;
-                                                       break;
-                                               }
-                                       }
-                                       
-                                       if (!element || element === contentContainer) {
-                                               break;
-                                       }
-                                       
-                                       element = element.parentNode;
-                               }
-                               
-                               if (allowScroll === false) {
-                                       event.preventDefault();
-                               }
-                       }, { passive: false });
-                       
-                       var content;
-                       if (element === null) {
-                               if (typeof html === 'string') {
-                                       content = elCreate('div');
-                                       content.id = id;
-                                       DomUtil.setInnerHtml(content, html);
-                               }
-                               else if (html instanceof DocumentFragment) {
-                                       var children = [], node;
-                                       for (var i = 0, length = html.childNodes.length; i < length; i++) {
-                                               node = html.childNodes[i];
-                                               
-                                               if (node.nodeType === Node.ELEMENT_NODE) {
-                                                       children.push(node);
-                                               }
-                                       }
-                                       
-                                       if (children[0].nodeName !== 'DIV' || children.length > 1) {
-                                               content = elCreate('div');
-                                               content.id = id;
-                                               content.appendChild(html);
-                                       }
-                                       else {
-                                               content = children[0];
-                                       }
-                               }
-                               else {
-                                       throw new TypeError("'html' must either be a string or a DocumentFragment");
-                               }
-                       }
-                       else {
-                               content = element;
-                       }
-                       
-                       contentContainer.appendChild(content);
-                       
-                       if (content.style.getPropertyValue('display') === 'none') {
-                               elShow(content);
-                       }
-                       
-                       _dialogs.set(id, {
-                               backdropCloseOnClick: options.backdropCloseOnClick,
-                               closable: options.closable,
-                               content: content,
-                               dialog: dialog,
-                               header: header,
-                               onBeforeClose: options.onBeforeClose,
-                               onClose: options.onClose,
-                               onShow: options.onShow,
-                               
-                               submitButton: null,
-                               inputFields: new List()
-                       });
-                       
-                       DomUtil.prepend(dialog, _container);
-                       
-                       if (typeof options.onSetup === 'function') {
-                               options.onSetup(content);
-                       }
-                       
-                       if (createOnly !== true) {
-                               this._updateDialog(id, null);
-                       }
-               },
-               
-               /**
-                * Updates the dialog's content element.
-                * 
-                * @param       {string}                id              element id
-                * @param       {?string}               html            content html, prevent changes by passing null
-                */
-               _updateDialog: function(id, html) {
-                       var data = _dialogs.get(id);
-                       if (data === undefined) {
-                               throw new Error("Expected a valid dialog id, '" + id + "' does not match any active dialog.");
-                       }
-                       
-                       if (typeof html === 'string') {
-                               DomUtil.setInnerHtml(data.content, html);
-                       }
-                       
-                       if (elAttr(data.dialog, 'aria-hidden') === 'true') {
-                               if (_callbackFocus === null) {
-                                       _callbackFocus = this._maintainFocus.bind(this);
-                                       document.body.addEventListener('focus', _callbackFocus, { capture: true });
-                               }
-                               
-                               if (data.closable && elAttr(_container, 'aria-hidden') === 'true') {
-                                       window.addEventListener('keyup', _keyupListener);
-                               }
-                               
-                               elAttr(data.dialog, 'aria-hidden', 'false');
-                               elAttr(_container, 'aria-hidden', 'false');
-                               elData(_container, 'close-on-click', (data.backdropCloseOnClick ? 'true' : 'false'));
-                               _activeDialog = id;
-                               
-                               // Keep a reference to the currently focused element to be able to restore it later.
-                               _focusedBeforeDialog = document.activeElement;
-                               
-                               // Set the focus to the first focusable child of the dialog element.
-                               var closeButton = elBySel('.dialogCloseButton', data.header);
-                               if (closeButton) elAttr(closeButton, 'inert', true);
-                               this._setFocusToFirstItem(data.dialog);
-                               if (closeButton) closeButton.removeAttribute('inert');
-                               
-                               if (typeof data.onShow === 'function') {
-                                       data.onShow(data.content);
-                               }
-                               
-                               if (elDataBool(data.content, 'is-static-dialog')) {
-                                       EventHandler.fire('com.woltlab.wcf.dialog', 'openStatic', {
-                                               content: data.content,
-                                               id: id
-                                       });
-                               }
-                               
-                               // close existing dropdowns
-                               UiSimpleDropdown.closeAll();
-                               window.WCF.Dropdown.Interactive.Handler.closeAll();
-                       }
-                       
-                       this.rebuild(id);
-                       
-                       DomChangeListener.trigger();
-               },
-               
-               /**
-                * @param {Event} event
-                */
-               _maintainFocus: function(event) {
-                       if (_activeDialog) {
-                               var data = _dialogs.get(_activeDialog);
-                               if (!data.dialog.contains(event.target) && !event.target.closest('.dropdownMenuContainer') && !event.target.closest('.datePicker')) {
-                                       this._setFocusToFirstItem(data.dialog, true);
-                               }
-                       }
-               },
-               
-               /**
-                * @param {Element} dialog
-                * @param {boolean} maintain
-                */
-               _setFocusToFirstItem: function(dialog, maintain) {
-                       var focusElement = this._getFirstFocusableChild(dialog);
-                       if (focusElement !== null) {
-                               if (maintain) {
-                                       if (focusElement.id === 'username' || focusElement.name === 'username') {
-                                               if (Environment.browser() === 'safari' && Environment.platform() === 'ios') {
-                                                       // iOS Safari's username/password autofill breaks if the input field is focused 
-                                                       focusElement = null;
-                                               }
-                                       }
-                               }
-                               
-                               if (focusElement) focusElement.focus();
-                       }
-               },
-               
-               /**
-                * @param {Element} node
-                * @returns {?Element}
-                */
-               _getFirstFocusableChild: function(node) {
-                       var nodeList = elBySelAll(_focusableElements.join(','), node);
-                       for (var i = 0, length = nodeList.length; i < length; i++) {
-                               if (nodeList[i].offsetWidth && nodeList[i].offsetHeight && nodeList[i].getClientRects().length) {
-                                       return nodeList[i];
-                               }
-                       }
-                       
-                       return null;    
-               },
-               
-               /**
-                * Rebuilds dialog identified by given id.
-                * 
-                * @param       {string}        id      element id
-                */
-               rebuild: function(id) {
-                       id = this._getDialogId(id);
-                       
-                       var data = _dialogs.get(id);
-                       if (data === undefined) {
-                               throw new Error("Expected a valid dialog id, '" + id + "' does not match any active dialog.");
-                       }
-                       
-                       // ignore non-active dialogs
-                       if (elAttr(data.dialog, 'aria-hidden') === 'true') {
-                               return;
-                       }
-                       
-                       var contentContainer = data.content.parentNode;
-                       
-                       var formSubmit = elBySel('.formSubmit', data.content);
-                       var unavailableHeight = 0;
-                       if (formSubmit !== null) {
-                               contentContainer.classList.add('dialogForm');
-                               formSubmit.classList.add('dialogFormSubmit');
-                               
-                               unavailableHeight += DomUtil.outerHeight(formSubmit);
-                               
-                               // Calculated height can be a fractional value and depending on the
-                               // browser the results can vary. By subtracting a single pixel we're
-                               // working around fractional values, without visually changing anything.
-                               unavailableHeight -= 1;
-                               
-                               contentContainer.style.setProperty('margin-bottom', unavailableHeight + 'px', '');
-                       }
-                       else {
-                               contentContainer.classList.remove('dialogForm');
-                               contentContainer.style.removeProperty('margin-bottom');
-                       }
-                       
-                       unavailableHeight += DomUtil.outerHeight(data.header);
-                       
-                       var maximumHeight = (window.innerHeight * (_dialogFullHeight ? 1 : 0.8)) - unavailableHeight;
-                       contentContainer.style.setProperty('max-height', ~~maximumHeight + 'px', '');
-                       
-                       // Chrome and Safari use heavy anti-aliasing when the dialog's width
-                       // cannot be evenly divided, causing the whole text to become blurry
-                       if (Environment.browser() === 'chrome' || Environment.browser() === 'safari') {
-                               // `clientWidth` will report an integer value that isn't rounded properly (e.g. 0.59 -> 0)
-                               var floatWidth = parseFloat(window.getComputedStyle(data.content).width);
-                               var needsFix = (Math.round(floatWidth) % 2) !== 0;
-                               
-                               data.content.parentNode.classList[(needsFix ? 'add' : 'remove')]('jsWebKitFractionalPixel');
-                       }
-                       
-                       var callbackObject = _dialogToObject.get(id);
-                       //noinspection JSUnresolvedVariable
-                       if (callbackObject !== undefined && typeof callbackObject._dialogSubmit === 'function') {
-                               var inputFields = elBySelAll('input[data-dialog-submit-on-enter="true"]', data.content);
-                               
-                               var submitButton = elBySel('.formSubmit > input[type="submit"], .formSubmit > button[data-type="submit"]', data.content);
-                               if (submitButton === null) {
-                                       // check if there is at least one input field with submit handling,
-                                       // otherwise we'll assume the dialog has not been populated yet
-                                       if (inputFields.length === 0) {
-                                               console.warn("Broken dialog, expected a submit button.", data.content);
-                                       }
-                                       
-                                       return;
-                               }
-                               
-                               if (data.submitButton !== submitButton) {
-                                       data.submitButton = submitButton;
-                                       
-                                       submitButton.addEventListener(WCF_CLICK_EVENT, (function (event) {
-                                               event.preventDefault();
-                                               
-                                               this._submit(id);
-                                       }).bind(this));
-                                       
-                                       // bind input fields
-                                       var inputField, _callbackKeydown = null;
-                                       for (var i = 0, length = inputFields.length; i < length; i++) {
-                                               inputField = inputFields[i];
-                                               
-                                               if (data.inputFields.has(inputField)) continue;
-                                               
-                                               if (_validInputTypes.indexOf(inputField.type) === -1) {
-                                                       console.warn("Unsupported input type.", inputField);
-                                                       continue;
-                                               }
-                                               
-                                               data.inputFields.add(inputField);
-                                               
-                                               if (_callbackKeydown === null) {
-                                                       _callbackKeydown = (function (event) {
-                                                               if (EventKey.Enter(event)) {
-                                                                       event.preventDefault();
-                                                                       
-                                                                       this._submit(id);
-                                                               }
-                                                       }).bind(this);
-                                               }
-                                               inputField.addEventListener('keydown', _callbackKeydown);
-                                       }
-                               }
-                       }
-               },
-               
-               /**
-                * Submits the dialog.
-                * 
-                * @param       {string}        id      dialog id
-                * @protected
-                */
-               _submit: function (id) {
-                       var data = _dialogs.get(id);
-                       
-                       var isValid = true;
-                       data.inputFields.forEach(function (inputField) {
-                               if (inputField.required) {
-                                       if (inputField.value.trim() === '') {
-                                               elInnerError(inputField, Language.get('wcf.global.form.error.empty'));
-                                               
-                                               isValid = false;
-                                       }
-                                       else {
-                                               elInnerError(inputField, false);
-                                       }
-                               }
-                       });
-                       
-                       if (isValid) {
-                               //noinspection JSUnresolvedFunction
-                               _dialogToObject.get(id)._dialogSubmit();
-                       }
-               },
-               
-               /**
-                * Handles clicks on the close button or the backdrop if enabled.
-                * 
-                * @param       {object}        event           click event
-                * @return      {boolean}       false if the event should be cancelled
-                */
-               _close: function(event) {
-                       event.preventDefault();
-                       
-                       var data = _dialogs.get(_activeDialog);
-                       if (typeof data.onBeforeClose === 'function') {
-                               data.onBeforeClose(_activeDialog);
-                               
-                               return false;
-                       }
-                       
-                       this.close(_activeDialog);
-               },
-               
-               /**
-                * Closes the current active dialog by clicks on the backdrop.
-                * 
-                * @param       {object}        event   event object
-                */
-               _closeOnBackdrop: function(event) {
-                       if (event.target !== _container) {
-                               return true;
-                       }
-                       
-                       if (elData(_container, 'close-on-click') === 'true') {
-                               this._close(event);
-                       }
-                       else {
-                               event.preventDefault();
-                       }
-               },
-               
-               /**
-                * Closes a dialog identified by given id.
-                * 
-                * @param       {(string|object)}       id      element id or callback object
-                */
-               close: function(id) {
-                       id = this._getDialogId(id);
-                       
-                       var data = _dialogs.get(id);
-                       if (data === undefined) {
-                               throw new Error("Expected a valid dialog id, '" + id + "' does not match any active dialog.");
-                       }
-                       
-                       elAttr(data.dialog, 'aria-hidden', 'true');
-                       
-                       // avoid keyboard focus on a now hidden element 
-                       if (document.activeElement.closest('.dialogContainer') === data.dialog) {
-                               document.activeElement.blur();
-                       }
-                       
-                       if (typeof data.onClose === 'function') {
-                               data.onClose(id);
-                       }
-                       
-                       // get next active dialog
-                       _activeDialog = null;
-                       for (var i = 0; i < _container.childElementCount; i++) {
-                               var child = _container.children[i];
-                               if (elAttr(child, 'aria-hidden') === 'false') {
-                                       _activeDialog = elData(child, 'id');
-                                       break;
-                               }
-                       }
-                       
-                       if (_activeDialog === null) {
-                               elAttr(_container, 'aria-hidden', 'true');
-                               elData(_container, 'close-on-click', 'false');
-                               
-                               if (data.closable) {
-                                       window.removeEventListener('keyup', _keyupListener);
-                               }
-                               
-                               UiScreen.pageOverlayClose();
-                       }
-                       else {
-                               data = _dialogs.get(_activeDialog);
-                               elData(_container, 'close-on-click', (data.backdropCloseOnClick ? 'true' : 'false'));
-                       }
-                       
-                       if (Environment.platform() !== 'desktop') {
-                               UiScreen.scrollEnable();
-                       }
-               },
-               
-               /**
-                * Returns the dialog data for given element id.
-                * 
-                * @param       {(string|object)}       id      element id or callback object
-                * @return      {(object|undefined)}    dialog data or undefined if element id is unknown
-                */
-               getDialog: function(id) {
-                       return _dialogs.get(this._getDialogId(id));
-               },
-               
-               /**
-                * Returns true for open dialogs.
-                * 
-                * @param       {(string|object)}       id      element id or callback object
-                * @return      {boolean}
-                */
-               isOpen: function(id) {
-                       var data = this.getDialog(id);
-                       return (data !== undefined && elAttr(data.dialog, 'aria-hidden') === 'false');
-               },
-               
-               /**
-                * Destroys a dialog instance.
-                * 
-                * @param       {Object}        callbackObject  the same object that was used to invoke `_dialogSetup()` on first call
-                */
-               destroy: function(callbackObject) {
-                       if (typeof callbackObject !== 'object' || callbackObject instanceof String) {
-                               throw new TypeError("Expected the callback object as parameter.");
-                       }
-                       
-                       if (_dialogObjects.has(callbackObject)) {
-                               var id = _dialogObjects.get(callbackObject).id;
-                               if (this.isOpen(id)) {
-                                       this.close(id);
-                               }
-                               
-                               _dialogs.delete(id);
-                               _dialogObjects.delete(callbackObject);
-                       }
-               },
-               
-               /**
-                * Returns a dialog's id.
-                * 
-                * @param       {(string|object)}       id      element id or callback object
-                * @return      {string}
-                * @protected
-                */
-               _getDialogId: function(id) {
-                       if (typeof id === 'object') {
-                               var dialogData = _dialogObjects.get(id);
-                               if (dialogData !== undefined) {
-                                       return dialogData.id;
-                               }
-                       }
-                       
-                       return id.toString();
-               },
-               
-               _ajaxSetup: function() {
-                       return {};
-               }
-       };
-});
-
-/**
- * Provides the AJAX status overlay.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ajax/Status
- */
-define('WoltLabSuite/Core/Ajax/Status',['Language'], function(Language) {
-       "use strict";
-       
-       var _activeRequests = 0;
-       var _overlay = null;
-       var _timeoutShow = null;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ajax/Status
-        */
-       var AjaxStatus = {
-               /**
-                * Initializes the status overlay on first usage.
-                */
-               _init: function() {
-                       _overlay = elCreate('div');
-                       _overlay.classList.add('spinner');
-                       elAttr(_overlay, 'role', 'status');
-                       
-                       var icon = elCreate('span');
-                       icon.className = 'icon icon48 fa-spinner';
-                       _overlay.appendChild(icon);
-                       
-                       var title = elCreate('span');
-                       title.textContent = Language.get('wcf.global.loading');
-                       _overlay.appendChild(title);
-                       
-                       document.body.appendChild(_overlay);
-               },
-               
-               /**
-                * Shows the loading overlay.
-                */
-               show: function() {
-                       if (_overlay === null) {
-                               this._init();
-                       }
-                       
-                       _activeRequests++;
-                       
-                       if (_timeoutShow === null) {
-                               _timeoutShow = window.setTimeout(function() {
-                                       if (_activeRequests) {
-                                               _overlay.classList.add('active');
-                                       }
-                                       
-                                       _timeoutShow = null;
-                               }, 250);
-                       }
-               },
-               
-               /**
-                * Hides the loading overlay.
-                */
-               hide: function() {
-                       _activeRequests--;
-                       
-                       if (_activeRequests === 0) {
-                               if (_timeoutShow !== null) {
-                                       window.clearTimeout(_timeoutShow);
-                               }
-                               
-                               _overlay.classList.remove('active');
-                       }
-               }
-       };
-       
-       return AjaxStatus;
-});
-
-/**
- * Versatile AJAX request handling.
- * 
- * In case you want to issue JSONP requests, please use `AjaxJsonp` instead.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ajax/Request
- */
-define('WoltLabSuite/Core/Ajax/Request',['Core', 'Language', 'Dom/ChangeListener', 'Dom/Util', 'Ui/Dialog', 'WoltLabSuite/Core/Ajax/Status'], function(Core, Language, DomChangeListener, DomUtil, UiDialog, AjaxStatus) {
-       "use strict";
-       
-       var _didInit = false;
-       var _ignoreAllErrors = false;
-       
-       /**
-        * @constructor
-        */
-       function AjaxRequest(options) {
-               this._data = null;
-               this._options = {};
-               this._previousXhr = null;
-               this._xhr = null;
-               
-               this._init(options);
-       }
-       AjaxRequest.prototype = {
-               /**
-                * Initializes the request options.
-                * 
-                * @param       {Object}        options         request options
-                */
-               _init: function(options) {
-                       this._options = Core.extend({
-                               // request data
-                               data: {},
-                               contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
-                               responseType: 'application/json',
-                               type: 'POST',
-                               url: '',
-                               withCredentials: false,
-                               
-                               // behavior
-                               autoAbort: false,
-                               ignoreError: false,
-                               pinData: false,
-                               silent: false,
-                               includeRequestedWith: true,
-                               
-                               // callbacks
-                               failure: null,
-                               finalize: null,
-                               success: null,
-                               progress: null,
-                               uploadProgress: null,
-                               
-                               callbackObject: null
-                       }, options);
-                       
-                       if (typeof options.callbackObject === 'object') {
-                               this._options.callbackObject = options.callbackObject;
-                       }
-                       
-                       this._options.url = Core.convertLegacyUrl(this._options.url);
-                       if (this._options.url.indexOf('index.php') === 0) {
-                               this._options.url = WSC_API_URL + this._options.url;
-                       }
-                       
-                       if (this._options.url.indexOf(WSC_API_URL) === 0) {
-                               this._options.includeRequestedWith = true;
-                               // always include credentials when querying the very own server
-                               this._options.withCredentials = true;
-                       }
-                       
-                       if (this._options.pinData) {
-                               this._data = Core.extend({}, this._options.data);
-                       }
-                       
-                       if (this._options.callbackObject !== null) {
-                               if (typeof this._options.callbackObject._ajaxFailure === 'function') this._options.failure = this._options.callbackObject._ajaxFailure.bind(this._options.callbackObject);
-                               if (typeof this._options.callbackObject._ajaxFinalize === 'function') this._options.finalize = this._options.callbackObject._ajaxFinalize.bind(this._options.callbackObject);
-                               if (typeof this._options.callbackObject._ajaxSuccess === 'function') this._options.success = this._options.callbackObject._ajaxSuccess.bind(this._options.callbackObject);
-                               if (typeof this._options.callbackObject._ajaxProgress === 'function') this._options.progress = this._options.callbackObject._ajaxProgress.bind(this._options.callbackObject);
-                               if (typeof this._options.callbackObject._ajaxUploadProgress === 'function') this._options.uploadProgress = this._options.callbackObject._ajaxUploadProgress.bind(this._options.callbackObject);
-                       }
-                       
-                       if (_didInit === false) {
-                               _didInit = true;
-                               
-                               window.addEventListener('beforeunload', function() { _ignoreAllErrors = true; });
-                       }
-               },
-               
-               /**
-                * Dispatches a request, optionally aborting a currently active request.
-                * 
-                * @param       {boolean}       abortPrevious   abort currently active request
-                */
-               sendRequest: function(abortPrevious) {
-                       if (abortPrevious === true || this._options.autoAbort) {
-                               this.abortPrevious();
-                       }
-                       
-                       if (!this._options.silent) {
-                               AjaxStatus.show();
-                       }
-                       
-                       if (this._xhr instanceof XMLHttpRequest) {
-                               this._previousXhr = this._xhr;
-                       }
-                       
-                       this._xhr = new XMLHttpRequest();
-                       this._xhr.open(this._options.type, this._options.url, true);
-                       if (this._options.contentType) {
-                               this._xhr.setRequestHeader('Content-Type', this._options.contentType);
-                       }
-                       if (this._options.withCredentials || this._options.includeRequestedWith) {
-                               this._xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
-                       }
-                       if (this._options.withCredentials) {
-                               this._xhr.withCredentials = true;
-                       }
-                       
-                       var self = this;
-                       var options = Core.clone(this._options);
-                       this._xhr.onload = function() {
-                               if (this.readyState === XMLHttpRequest.DONE) {
-                                       if (this.status >= 200 && this.status < 300 || this.status === 304) {
-                                               if (options.responseType && this.getResponseHeader('Content-Type').indexOf(options.responseType) !== 0) {
-                                                       // request succeeded but invalid response type
-                                                       self._failure(this, options);
-                                               }
-                                               else {
-                                                       self._success(this, options);
-                                               }
-                                       }
-                                       else {
-                                               self._failure(this, options);
-                                       }
-                               }
-                       };
-                       this._xhr.onerror = function() {
-                               self._failure(this, options);
-                       };
-                       
-                       if (this._options.progress) {
-                               this._xhr.onprogress = this._options.progress;
-                       }
-                       if (this._options.uploadProgress) {
-                               this._xhr.upload.onprogress = this._options.uploadProgress;
-                       }
-                       
-                       if (this._options.type === 'POST') {
-                               var data = this._options.data;
-                               if (typeof data === 'object' && Core.getType(data) !== 'FormData') {
-                                       data = Core.serialize(data);
-                               }
-                               
-                               this._xhr.send(data);
-                       }
-                       else {
-                               this._xhr.send();
-                       }
-               },
-               
-               /**
-                * Aborts a previous request.
-                */
-               abortPrevious: function() {
-                       if (this._previousXhr === null) {
-                               return;
-                       }
-                       
-                       this._previousXhr.abort();
-                       this._previousXhr = null;
-                       
-                       if (!this._options.silent) {
-                               AjaxStatus.hide();
-                       }
-               },
-               
-               /**
-                * Sets a specific option.
-                * 
-                * @param       {string}        key     option name
-                * @param       {?}             value   option value
-                */
-               setOption: function(key, value) {
-                       this._options[key] = value;
-               },
-               
-               /**
-                * Returns an option by key or undefined.
-                * 
-                * @param       {string}        key     option name
-                * @return      {(*|null)}      option value or null
-                */
-               getOption: function(key) {
-                       if (objOwns(this._options, key)) {
-                               return this._options[key];
-                       }
-                       
-                       return null;
-               },
-               
-               /**
-                * Sets request data while honoring pinned data from setup callback.
-                * 
-                * @param       {Object}        data    request data
-                */
-               setData: function(data) {
-                       if (this._data !== null && Core.getType(data) !== 'FormData') {
-                               data = Core.extend(this._data, data);
-                       }
-                       
-                       this._options.data = data;
-               },
-               
-               /**
-                * Handles a successful request.
-                * 
-                * @param       {XMLHttpRequest}        xhr             request object
-                * @param       {Object}                options         request options
-                */
-               _success: function(xhr, options) {
-                       if (!options.silent) {
-                               AjaxStatus.hide();
-                       }
-                       
-                       if (typeof options.success === 'function') {
-                               var data = null;
-                               if (xhr.getResponseHeader('Content-Type') === 'application/json') {
-                                       try {
-                                               data = JSON.parse(xhr.responseText);
-                                       }
-                                       catch (e) {
-                                               // invalid JSON
-                                               this._failure(xhr, options);
-                                               
-                                               return;
-                                       }
-                                       
-                                       // trim HTML before processing, see http://jquery.com/upgrade-guide/1.9/#jquery-htmlstring-versus-jquery-selectorstring
-                                       if (data && data.returnValues && data.returnValues.template !== undefined) {
-                                               data.returnValues.template = data.returnValues.template.trim();
-                                       }
-                                       
-                                       // force-invoke the background queue
-                                       if (data && data.forceBackgroundQueuePerform) {
-                                               require(['WoltLabSuite/Core/BackgroundQueue'], function(BackgroundQueue) {
-                                                       BackgroundQueue.invoke();
-                                               });
-                                       }
-                               }
-                               
-                               options.success(data, xhr.responseText, xhr, options.data);
-                       }
-                       
-                       this._finalize(options);
-               },
-               
-               /**
-                * Handles failed requests, this can be both a successful request with
-                * a non-success status code or an entirely failed request.
-                * 
-                * @param       {XMLHttpRequest}        xhr             request object
-                * @param       {Object}                options         request options
-                */
-               _failure: function (xhr, options) {
-                       if (_ignoreAllErrors) {
-                               return;
-                       }
-                       
-                       if (!options.silent) {
-                               AjaxStatus.hide();
-                       }
-                       
-                       var data = null;
-                       try {
-                               data = JSON.parse(xhr.responseText);
-                       }
-                       catch (e) {}
-                       
-                       var showError = true;
-                       if (typeof options.failure === 'function') {
-                               showError = options.failure((data || {}), (xhr.responseText || ''), xhr, options.data);
-                       }
-                       
-                       if (options.ignoreError !== true && showError !== false) {
-                               var html = this.getErrorHtml(data, xhr);
-                               
-                               if (html) {
-                                       if (UiDialog === undefined) UiDialog = require('Ui/Dialog');
-                                       UiDialog.openStatic(DomUtil.getUniqueId(), html, {
-                                               title: Language.get('wcf.global.error.title')
-                                       });
-                               }
-                       }
-                       
-                       this._finalize(options);
-               },
-               
-               /**
-                * Returns the inner HTML for an error/exception display.
-                * 
-                * @param       {Object}                data
-                * @param       {XMLHttpRequest}        xhr
-                * @return      {string}
-                */
-               getErrorHtml: function(data, xhr) {
-                       var details = '';
-                       var message = '';
-                       
-                       if (data !== null) {
-                               if (data.file && data.line) {
-                                       details += '<br><p>File:</p><p>' + data.file + ' in line ' + data.line + '</p>';
-                               }
-                               
-                               if (data.stacktrace) details += '<br><p>Stacktrace:</p><p>' + data.stacktrace + '</p>';
-                               else if (data.exceptionID) details += '<br><p>Exception ID: <code>' + data.exceptionID + '</code></p>';
-                               
-                               message = data.message;
-                               
-                               data.previous.forEach(function(previous) {
-                                       details += '<hr><p>' + previous.message + '</p>';
-                                       details += '<br><p>Stacktrace</p><p>' + previous.stacktrace + '</p>';
-                               });
-                       }
-                       else {
-                               message = xhr.responseText;
-                       }
-                       
-                       if (!message || message === 'undefined') {
-                               if (!ENABLE_DEBUG_MODE && !ENABLE_PRODUCTION_DEBUG_MODE) return null;
-                               
-                               message = 'XMLHttpRequest failed without a responseText. Check your browser console.'
-                       }
-                       
-                       return '<div class="ajaxDebugMessage"><p>' + message + '</p>' + details + '</div>';
-               },
-               
-               /**
-                * Finalizes a request.
-                * 
-                * @param       {Object}        options         request options
-                */
-               _finalize: function(options) {
-                       if (typeof options.finalize === 'function') {
-                               options.finalize(this._xhr);
-                       }
-                       
-                       this._previousXhr = null;
-                       
-                       DomChangeListener.trigger();
-                       
-                       // fix anchor tags generated through WCF::getAnchor()
-                       var links = elBySelAll('a[href*="#"]');
-                       for (var i = 0, length = links.length; i < length; i++) {
-                               var link = links[i];
-                               var href = elAttr(link, 'href');
-                               if (href.indexOf('AJAXProxy') !== -1 || href.indexOf('ajax-proxy') !== -1) {
-                                       href = href.substr(href.indexOf('#'));
-                                       elAttr(link, 'href', document.location.toString().replace(/#.*/, '') + href);
-                               }
-                       }
-               }
-       };
-       
-       return AjaxRequest;
-});
-
-/**
- * Handles AJAX requests.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ajax
- */
-define('WoltLabSuite/Core/Ajax',['AjaxRequest', 'Core', 'ObjectMap'], function(AjaxRequest, Core, ObjectMap) {
-       "use strict";
-       
-       var _requests = new ObjectMap();
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ajax
-        */
-       return {
-               /**
-                * Shorthand function to perform a request against the WCF-API with overrides
-                * for success and failure callbacks.
-                * 
-                * @param       {object}                callbackObject  callback object
-                * @param       {object<string, *>=}    data            request data
-                * @param       {function=}             success         success callback
-                * @param       {function=}             failure         failure callback
-                * @return      {AjaxRequest}
-                */
-               api: function(callbackObject, data, success, failure) {
-                       // Fetch AjaxRequest, as it cannot be provided because of a circular dependency
-                       if (AjaxRequest === undefined) AjaxRequest = require('AjaxRequest');
-                       
-                       if (typeof data !== 'object') data = {};
-                       
-                       var request = _requests.get(callbackObject);
-                       if (request === undefined) {
-                               if (typeof callbackObject._ajaxSetup !== 'function') {
-                                       throw new TypeError("Callback object must implement at least _ajaxSetup().");
-                               }
-                               
-                               var options = callbackObject._ajaxSetup();
-                               
-                               options.pinData = true;
-                               options.callbackObject = callbackObject;
-                               
-                               if (!options.url) {
-                                       options.url = 'index.php?ajax-proxy/&t=' + SECURITY_TOKEN;
-                                       options.withCredentials = true;
-                               }
-                               
-                               request = new AjaxRequest(options);
-                               
-                               _requests.set(callbackObject, request);
-                       }
-                       
-                       var oldSuccess = null;
-                       var oldFailure = null;
-                       
-                       if (typeof success === 'function') {
-                               oldSuccess = request.getOption('success');
-                               request.setOption('success', success);
-                       }
-                       if (typeof failure === 'function') {
-                               oldFailure = request.getOption('failure');
-                               request.setOption('failure', failure);
-                       }
-                       
-                       request.setData(data);
-                       request.sendRequest();
-                       
-                       // restore callbacks
-                       if (oldSuccess !== null) request.setOption('success', oldSuccess);
-                       if (oldFailure !== null) request.setOption('failure', oldFailure);
-                       
-                       return request;
-               },
-               
-               /**
-                * Shorthand function to perform a single request against the WCF-API.
-                * 
-                * Please use `Ajax.api` if you're about to repeatedly send requests because this
-                * method will spawn an new and rather expensive `AjaxRequest` with each call.
-                *  
-                * @param       {object<string, *>}     options         request options
-                */
-               apiOnce: function(options) {
-                       // Fetch AjaxRequest, as it cannot be provided because of a circular dependency
-                       if (AjaxRequest === undefined) AjaxRequest = require('AjaxRequest');
-                       
-                       options.pinData = false;
-                       options.callbackObject = null;
-                       if (!options.url) {
-                               options.url = 'index.php?ajax-proxy/&t=' + SECURITY_TOKEN;
-                               options.withCredentials = true;
-                       }
-                       
-                       var request = new AjaxRequest(options);
-                       request.sendRequest(false);
-               },
-               
-               /**
-                * Returns the request object used for an earlier call to `api()`.
-                * 
-                * @param       {Object}        callbackObject  callback object
-                * @return      {AjaxRequest}
-                */
-               getRequestObject: function(callbackObject) {
-                       if (!_requests.has(callbackObject)) {
-                               throw new Error('Expected a previously used callback object, provided object is unknown.');
-                       }
-                       
-                       return _requests.get(callbackObject);
-               }
-       };
-});
-
-/**
- * Manages the invocation of the background queue.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/BackgroundQueue
- */
-define('WoltLabSuite/Core/BackgroundQueue',['Ajax'], function(Ajax) {
-       "use strict";
-       
-       var _invocations = 0;
-       var _isBusy = false;
-       var _url = '';
-       
-       /**
-        * @exports     WoltLabSuite/Core/BackgroundQueue
-        */
-       return {
-               /**
-                * Sets the url of the background queue perform action.
-                * 
-                * @param       {string}        url     background queue perform url
-                */
-               setUrl: function (url) {
-                       _url = url;
-               },
-               
-               /**
-                * Invokes the background queue.
-                */
-               invoke: function () {
-                       if (_url === '') {
-                               console.error('The background queue has not been initialized yet.');
-                               return;
-                       }
-                       
-                       if (_isBusy) return;
-                       
-                       _isBusy = true;
-                       
-                       Ajax.api(this);
-               },
-               
-               _ajaxSuccess: function (data) {
-                       _invocations++;
-                       
-                       // invoke the queue up to 5 times in a row
-                       if (data > 0 && _invocations < 5) {
-                               window.setTimeout(function () {
-                                       _isBusy = false;
-                                       this.invoke();
-                               }.bind(this), 1000);
-                       }
-                       else {
-                               _isBusy = false;
-                               _invocations = 0;
-                       }
-               },
-               
-               _ajaxSetup: function () {
-                       return {
-                               url: _url,
-                               ignoreError: true,
-                               silent: true
-                       }
-               }
-       }
-});
-
-/**
- * @license MIT or GPL-2.0
- * @fileOverview Favico animations
- * @author Miroslav Magda, http://blog.ejci.net
- * @source: https://github.com/ejci/favico.js
- * @version 0.3.10
- */
-
-/**
- * Create new favico instance
- * @param {Object} Options
- * @return {Object} Favico object
- * @example
- * var favico = new Favico({
- *    bgColor : '#d00',
- *    textColor : '#fff',
- *    fontFamily : 'sans-serif',
- *    fontStyle : 'bold',
- *    type : 'circle',
- *    position : 'down',
- *    animation : 'slide',
- *    elementId: false,
- *    element: null,
- *    dataUrl: function(url){},
- *    win: window
- * });
- */
-(function () {
-
-       var Favico = (function (opt) {
-               'use strict';
-               opt = (opt) ? opt : {};
-               var _def = {
-                       bgColor: '#d00',
-                       textColor: '#fff',
-                       fontFamily: 'sans-serif', //Arial,Verdana,Times New Roman,serif,sans-serif,...
-                       fontStyle: 'bold', //normal,italic,oblique,bold,bolder,lighter,100,200,300,400,500,600,700,800,900
-                       type: 'circle',
-                       position: 'down', // down, up, left, leftup (upleft)
-                       animation: 'slide',
-                       elementId: false,
-                       element: null,
-                       dataUrl: false,
-                       win: window
-               };
-               var _opt, _orig, _h, _w, _canvas, _context, _img, _ready, _lastBadge, _running, _readyCb, _stop, _browser, _animTimeout, _drawTimeout, _doc;
-
-               _browser = {};
-               _browser.ff = typeof InstallTrigger != 'undefined';
-               _browser.chrome = !!window.chrome;
-               _browser.opera = !!window.opera || navigator.userAgent.indexOf('Opera') >= 0;
-               _browser.ie = /*@cc_on!@*/false;
-               _browser.safari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0;
-               _browser.supported = (_browser.chrome || _browser.ff || _browser.opera);
-
-               var _queue = [];
-               _readyCb = function () {
-               };
-               _ready = _stop = false;
-               /**
-                * Initialize favico
-                */
-               var init = function () {
-                       //merge initial options
-                       _opt = merge(_def, opt);
-                       _opt.bgColor = hexToRgb(_opt.bgColor);
-                       _opt.textColor = hexToRgb(_opt.textColor);
-                       _opt.position = _opt.position.toLowerCase();
-                       _opt.animation = (animation.types['' + _opt.animation]) ? _opt.animation : _def.animation;
-
-                       _doc = _opt.win.document;
-
-                       var isUp = _opt.position.indexOf('up') > -1;
-                       var isLeft = _opt.position.indexOf('left') > -1;
-
-                       //transform the animations
-                       if (isUp || isLeft) {
-                               for (var a in animation.types) {
-                                       for (var i = 0; i < animation.types[a].length; i++) {
-                                               var step = animation.types[a][i];
-
-                                               if (isUp) {
-                                                       if (step.y < 0.6) {
-                                                               step.y = step.y - 0.4;
-                                                       } else {
-                                                               step.y = step.y - 2 * step.y + (1 - step.w);
-                                                       }
-                                               }
-
-                                               if (isLeft) {
-                                                       if (step.x < 0.6) {
-                                                               step.x = step.x - 0.4;
-                                                       } else {
-                                                               step.x = step.x - 2 * step.x + (1 - step.h);
-                                                       }
-                                               }
-
-                                               animation.types[a][i] = step;
-                                       }
-                               }
-                       }
-                       _opt.type = (type['' + _opt.type]) ? _opt.type : _def.type;
-
-                       _orig = link. getIcons();
-                       //create temp canvas
-                       _canvas = document.createElement('canvas');
-                       //create temp image
-                       _img = document.createElement('img');
-                       var lastIcon = _orig[_orig.length - 1];
-                       if (lastIcon.hasAttribute('href')) {
-                               _img.setAttribute('crossOrigin', 'anonymous');
-                               //get width/height
-                               _img.onload = function () {
-                                       _h = (_img.height > 0) ? _img.height : 32;
-                                       _w = (_img.width > 0) ? _img.width : 32;
-                                       _canvas.height = _h;
-                                       _canvas.width = _w;
-                                       _context = _canvas.getContext('2d');
-                                       icon.ready();
-                               };
-                               _img.setAttribute('src', lastIcon.getAttribute('href'));
-                       } else {
-                               _h = 32;
-                               _w = 32;
-                               _img.height = _h;
-                               _img.width = _w;
-                               _canvas.height = _h;
-                               _canvas.width = _w;
-                               _context = _canvas.getContext('2d');
-                               icon.ready();
-                       }
-
-               };
-               /**
-                * Icon namespace
-                */
-               var icon = {};
-               /**
-                * Icon is ready (reset icon) and start animation (if ther is any)
-                */
-               icon.ready = function () {
-                       _ready = true;
-                       icon.reset();
-                       _readyCb();
-               };
-               /**
-                * Reset icon to default state
-                */
-               icon.reset = function () {
-                       //reset
-                       if (!_ready) {
-                               return;
-                       }
-                       _queue = [];
-                       _lastBadge = false;
-                       _running = false;
-                       _context.clearRect(0, 0, _w, _h);
-                       _context.drawImage(_img, 0, 0, _w, _h);
-                       //_stop=true;
-                       link.setIcon(_canvas);
-                       //webcam('stop');
-                       //video('stop');
-                       window.clearTimeout(_animTimeout);
-                       window.clearTimeout(_drawTimeout);
-               };
-               /**
-                * Start animation
-                */
-               icon.start = function () {
-                       if (!_ready || _running) {
-                               return;
-                       }
-                       var finished = function () {
-                               _lastBadge = _queue[0];
-                               _running = false;
-                               if (_queue.length > 0) {
-                                       _queue.shift();
-                                       icon.start();
-                               } else {
-
-                               }
-                       };
-                       if (_queue.length > 0) {
-                               _running = true;
-                               var run = function () {
-                                       // apply options for this animation
-                                       ['type', 'animation', 'bgColor', 'textColor', 'fontFamily', 'fontStyle'].forEach(function (a) {
-                                               if (a in _queue[0].options) {
-                                                       _opt[a] = _queue[0].options[a];
-                                               }
-                                       });
-                                       animation.run(_queue[0].options, function () {
-                                               finished();
-                                       }, false);
-                               };
-                               if (_lastBadge) {
-                                       animation.run(_lastBadge.options, function () {
-                                               run();
-                                       }, true);
-                               } else {
-                                       run();
-                               }
-                       }
-               };
-
-               /**
-                * Badge types
-                */
-               var type = {};
-               var options = function (opt) {
-                       opt.n = ((typeof opt.n) === 'number') ? Math.abs(opt.n | 0) : opt.n;
-                       opt.x = _w * opt.x;
-                       opt.y = _h * opt.y;
-                       opt.w = _w * opt.w;
-                       opt.h = _h * opt.h;
-                       opt.len = ("" + opt.n).length;
-                       return opt;
-               };
-               /**
-                * Generate circle
-                * @param {Object} opt Badge options
-                */
-               type.circle = function (opt) {
-                       opt = options(opt);
-                       var more = false;
-                       if (opt.len === 2) {
-                               opt.x = opt.x - opt.w * 0.4;
-                               opt.w = opt.w * 1.4;
-                               more = true;
-                       } else if (opt.len >= 3) {
-                               opt.x = opt.x - opt.w * 0.65;
-                               opt.w = opt.w * 1.65;
-                               more = true;
-                       }
-                       _context.clearRect(0, 0, _w, _h);
-                       _context.drawImage(_img, 0, 0, _w, _h);
-                       _context.beginPath();
-                       _context.font = _opt.fontStyle + " " + Math.floor(opt.h * (opt.n > 99 ? 0.85 : 1)) + "px " + _opt.fontFamily;
-                       _context.textAlign = 'center';
-                       if (more) {
-                               _context.moveTo(opt.x + opt.w / 2, opt.y);
-                               _context.lineTo(opt.x + opt.w - opt.h / 2, opt.y);
-                               _context.quadraticCurveTo(opt.x + opt.w, opt.y, opt.x + opt.w, opt.y + opt.h / 2);
-                               _context.lineTo(opt.x + opt.w, opt.y + opt.h - opt.h / 2);
-                               _context.quadraticCurveTo(opt.x + opt.w, opt.y + opt.h, opt.x + opt.w - opt.h / 2, opt.y + opt.h);
-                               _context.lineTo(opt.x + opt.h / 2, opt.y + opt.h);
-                               _context.quadraticCurveTo(opt.x, opt.y + opt.h, opt.x, opt.y + opt.h - opt.h / 2);
-                               _context.lineTo(opt.x, opt.y + opt.h / 2);
-                               _context.quadraticCurveTo(opt.x, opt.y, opt.x + opt.h / 2, opt.y);
-                       } else {
-                               _context.arc(opt.x + opt.w / 2, opt.y + opt.h / 2, opt.h / 2, 0, 2 * Math.PI);
-                       }
-                       _context.fillStyle = 'rgba(' + _opt.bgColor.r + ',' + _opt.bgColor.g + ',' + _opt.bgColor.b + ',' + opt.o + ')';
-                       _context.fill();
-                       _context.closePath();
-                       _context.beginPath();
-                       _context.stroke();
-                       _context.fillStyle = 'rgba(' + _opt.textColor.r + ',' + _opt.textColor.g + ',' + _opt.textColor.b + ',' + opt.o + ')';
-                       //_context.fillText((more) ? '9+' : opt.n, Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.15));
-                       if ((typeof opt.n) === 'number' && opt.n > 999) {
-                               _context.fillText(((opt.n > 9999) ? 9 : Math.floor(opt.n / 1000)) + 'k+', Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.2));
-                       } else {
-                               _context.fillText(opt.n, Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.15));
-                       }
-                       _context.closePath();
-               };
-               /**
-                * Generate rectangle
-                * @param {Object} opt Badge options
-                */
-               type.rectangle = function (opt) {
-                       opt = options(opt);
-                       var more = false;
-                       if (opt.len === 2) {
-                               opt.x = opt.x - opt.w * 0.4;
-                               opt.w = opt.w * 1.4;
-                               more = true;
-                       } else if (opt.len >= 3) {
-                               opt.x = opt.x - opt.w * 0.65;
-                               opt.w = opt.w * 1.65;
-                               more = true;
-                       }
-                       _context.clearRect(0, 0, _w, _h);
-                       _context.drawImage(_img, 0, 0, _w, _h);
-                       _context.beginPath();
-                       _context.font = _opt.fontStyle + " " + Math.floor(opt.h * (opt.n > 99 ? 0.9 : 1)) + "px " + _opt.fontFamily;
-                       _context.textAlign = 'center';
-                       _context.fillStyle = 'rgba(' + _opt.bgColor.r + ',' + _opt.bgColor.g + ',' + _opt.bgColor.b + ',' + opt.o + ')';
-                       _context.fillRect(opt.x, opt.y, opt.w, opt.h);
-                       _context.fillStyle = 'rgba(' + _opt.textColor.r + ',' + _opt.textColor.g + ',' + _opt.textColor.b + ',' + opt.o + ')';
-                       //_context.fillText((more) ? '9+' : opt.n, Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.15));
-                       if ((typeof opt.n) === 'number' && opt.n > 999) {
-                               _context.fillText(((opt.n > 9999) ? 9 : Math.floor(opt.n / 1000)) + 'k+', Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.2));
-                       } else {
-                               _context.fillText(opt.n, Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.15));
-                       }
-                       _context.closePath();
-               };
-
-               /**
-                * Set badge
-                */
-               var badge = function (number, opts) {
-                       opts = ((typeof opts) === 'string' ? {
-                               animation: opts
-                       } : opts) || {};
-                       _readyCb = function () {
-                               try {
-                                       if (typeof (number) === 'number' ? (number > 0) : (number !== '')) {
-                                               var q = {
-                                                       type: 'badge',
-                                                       options: {
-                                                               n: number
-                                                       }
-                                               };
-                                               if ('animation' in opts && animation.types['' + opts.animation]) {
-                                                       q.options.animation = '' + opts.animation;
-                                               }
-                                               if ('type' in opts && type['' + opts.type]) {
-                                                       q.options.type = '' + opts.type;
-                                               }
-                                               ['bgColor', 'textColor'].forEach(function (o) {
-                                                       if (o in opts) {
-                                                               q.options[o] = hexToRgb(opts[o]);
-                                                       }
-                                               });
-                                               ['fontStyle', 'fontFamily'].forEach(function (o) {
-                                                       if (o in opts) {
-                                                               q.options[o] = opts[o];
-                                                       }
-                                               });
-                                               _queue.push(q);
-                                               if (_queue.length > 100) {
-                                                       throw new Error('Too many badges requests in queue.');
-                                               }
-                                               icon.start();
-                                       } else {
-                                               icon.reset();
-                                       }
-                               } catch (e) {
-                                       throw new Error('Error setting badge. Message: ' + e.message);
-                               }
-                       };
-                       if (_ready) {
-                               _readyCb();
-                       }
-               };
-
-               /**
-                * Set image as icon
-                */
-               var image = function (imageElement) {
-                       _readyCb = function () {
-                               try {
-                                       var w = imageElement.width;
-                                       var h = imageElement.height;
-                                       var newImg = document.createElement('img');
-                                       var ratio = (w / _w < h / _h) ? (w / _w) : (h / _h);
-                                       newImg.setAttribute('crossOrigin', 'anonymous');
-                                       newImg.onload=function(){
-                                               _context.clearRect(0, 0, _w, _h);
-                                               _context.drawImage(newImg, 0, 0, _w, _h);
-                                               link.setIcon(_canvas);
-                                       };
-                                       newImg.setAttribute('src', imageElement.getAttribute('src'));
-                                       newImg.height = (h / ratio);
-                                       newImg.width = (w / ratio);
-                               } catch (e) {
-                                       throw new Error('Error setting image. Message: ' + e.message);
-                               }
-                       };
-                       if (_ready) {
-                               _readyCb();
-                       }
-               };
-               /**
-                * Set the icon from a source url. Won't work with badges.
-                */
-               var rawImageSrc = function (url) {
-                       _readyCb = function() {
-                               link.setIconSrc(url);
-                       };
-                       if (_ready) {
-                               _readyCb();
-                       }
-               };
-               /**
-                * Set video as icon
-                */
-               var video = function (videoElement) {
-                       _readyCb = function () {
-                               try {
-                                       if (videoElement === 'stop') {
-                                               _stop = true;
-                                               icon.reset();
-                                               _stop = false;
-                                               return;
-                                       }
-                                       //var w = videoElement.width;
-                                       //var h = videoElement.height;
-                                       //var ratio = (w / _w < h / _h) ? (w / _w) : (h / _h);
-                                       videoElement.addEventListener('play', function () {
-                                               drawVideo(this);
-                                       }, false);
-
-                               } catch (e) {
-                                       throw new Error('Error setting video. Message: ' + e.message);
-                               }
-                       };
-                       if (_ready) {
-                               _readyCb();
-                       }
-               };
-               /**
-                * Set video as icon
-                */
-               var webcam = function (action) {
-                       //UR
-                       if (!window.URL || !window.URL.createObjectURL) {
-                               window.URL = window.URL || {};
-                               window.URL.createObjectURL = function (obj) {
-                                       return obj;
-                               };
-                       }
-                       if (_browser.supported) {
-                               var newVideo = false;
-                               navigator.getUserMedia = navigator.getUserMedia || navigator.oGetUserMedia || navigator.msGetUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia;
-                               _readyCb = function () {
-                                       try {
-                                               if (action === 'stop') {
-                                                       _stop = true;
-                                                       icon.reset();
-                                                       _stop = false;
-                                                       return;
-                                               }
-                                               newVideo = document.createElement('video');
-                                               newVideo.width = _w;
-                                               newVideo.height = _h;
-                                               navigator.getUserMedia({
-                                                       video: true,
-                                                       audio: false
-                                               }, function (stream) {
-                                                       newVideo.src = URL.createObjectURL(stream);
-                                                       newVideo.play();
-                                                       drawVideo(newVideo);
-                                               }, function () {
-                                               });
-                                       } catch (e) {
-                                               throw new Error('Error setting webcam. Message: ' + e.message);
-                                       }
-                               };
-                               if (_ready) {
-                                       _readyCb();
-                               }
-                       }
-
-               };
-
-               var setOpt = function (key, value) {
-                       var opts = key;
-                       if (!(value == null && Object.prototype.toString.call(key) == '[object Object]')) {
-                               opts = {};
-                               opts[key] = value;
-                       }
-
-                       var keys = Object.keys(opts);
-                       for (var i = 0; i < keys.length; i++) {
-                               if (keys[i] == 'bgColor' || keys[i] == 'textColor') {
-                                       _opt[keys[i]] = hexToRgb(opts[keys[i]]);
-                               } else {
-                                       _opt[keys[i]] = opts[keys[i]];
-                               }
-                       }
-
-                       _queue.push(_lastBadge);
-                       icon.start();
-               };
-
-               /**
-                * Draw video to context and repeat :)
-                */
-               function drawVideo(video) {
-                       if (video.paused || video.ended || _stop) {
-                               return false;
-                       }
-                       //nasty hack for FF webcam (Thanks to Julian Ä†wirko, kontakt@redsunmedia.pl)
-                       try {
-                               _context.clearRect(0, 0, _w, _h);
-                               _context.drawImage(video, 0, 0, _w, _h);
-                       } catch (e) {
-
-                       }
-                       _drawTimeout = setTimeout(function () {
-                               drawVideo(video);
-                       }, animation.duration);
-                       link.setIcon(_canvas);
-               }
-
-               var link = {};
-               /**
-                * Get icons from HEAD tag or create a new <link> element
-                */
-               link.getIcons = function () {
-                       var elms = [];
-                       //get link element
-                       var getLinks = function () {
-                               var icons = [];
-                               var links = _doc.getElementsByTagName('head')[0].getElementsByTagName('link');
-                               for (var i = 0; i < links.length; i++) {
-                                       if ((/(^|\s)icon(\s|$)/i).test(links[i].getAttribute('rel'))) {
-                                               icons.push(links[i]);
-                                       }
-                               }
-                               return icons;
-                       };
-                       if (_opt.element) {
-                               elms = [_opt.element];
-                       } else if (_opt.elementId) {
-                               //if img element identified by elementId
-                               elms = [_doc.getElementById(_opt.elementId)];
-                               elms[0].setAttribute('href', elms[0].getAttribute('src'));
-                       } else {
-                               //if link element
-                               elms = getLinks();
-                               if (elms.length === 0) {
-                                       elms = [_doc.createElement('link')];
-                                       elms[0].setAttribute('rel', 'icon');
-                                       _doc.getElementsByTagName('head')[0].appendChild(elms[0]);
-                               }
-                       }
-                       elms.forEach(function(item) {
-                               item.setAttribute('type', 'image/png');
-                       });
-                       return elms;
-               };
-               link.setIcon = function (canvas) {
-                       var url = canvas.toDataURL('image/png');
-                       link.setIconSrc(url);
-               };
-               link.setIconSrc = function (url) {
-                       if (_opt.dataUrl) {
-                               //if using custom exporter
-                               _opt.dataUrl(url);
-                       }
-                       if (_opt.element) {
-                               _opt.element.setAttribute('href', url);
-                               _opt.element.setAttribute('src', url);
-                       } else if (_opt.elementId) {
-                               //if is attached to element (image)
-                               var elm = _doc.getElementById(_opt.elementId);
-                               elm.setAttribute('href', url);
-                               elm.setAttribute('src', url);
-                       } else {
-                               //if is attached to fav icon
-                               if (_browser.ff || _browser.opera) {
-                                       //for FF we need to "recreate" element, atach to dom and remove old <link>
-                                       //var originalType = _orig.getAttribute('rel');
-                                       var old = _orig[_orig.length - 1];
-                                       var newIcon = _doc.createElement('link');
-                                       _orig = [newIcon];
-                                       //_orig.setAttribute('rel', originalType);
-                                       if (_browser.opera) {
-                                               newIcon.setAttribute('rel', 'icon');
-                                       }
-                                       newIcon.setAttribute('rel', 'icon');
-                                       newIcon.setAttribute('type', 'image/png');
-                                       _doc.getElementsByTagName('head')[0].appendChild(newIcon);
-                                       newIcon.setAttribute('href', url);
-                                       if (old.parentNode) {
-                                               old.parentNode.removeChild(old);
-                                       }
-                               } else {
-                                       _orig.forEach(function(icon) {
-                                               icon.setAttribute('href', url);
-                                       });
-                               }
-                       }
-               };
-
-               //http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb#answer-5624139
-               //HEX to RGB convertor
-               function hexToRgb(hex) {
-                       var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
-                       hex = hex.replace(shorthandRegex, function (m, r, g, b) {
-                               return r + r + g + g + b + b;
-                       });
-                       var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
-                       return result ? {
-                               r: parseInt(result[1], 16),
-                               g: parseInt(result[2], 16),
-                               b: parseInt(result[3], 16)
-                       } : false;
-               }
-
-               /**
-                * Merge options
-                */
-               function merge(def, opt) {
-                       var mergedOpt = {};
-                       var attrname;
-                       for (attrname in def) {
-                               mergedOpt[attrname] = def[attrname];
-                       }
-                       for (attrname in opt) {
-                               mergedOpt[attrname] = opt[attrname];
-                       }
-                       return mergedOpt;
-               }
-
-               /**
-                * Cross-browser page visibility shim
-                * http://stackoverflow.com/questions/12536562/detect-whether-a-window-is-visible
-                */
-               function isPageHidden() {
-                       return _doc.hidden || _doc.msHidden || _doc.webkitHidden || _doc.mozHidden;
-               }
-
-               /**
-                * @namespace animation
-                */
-               var animation = {};
-               /**
-                * Animation "frame" duration
-                */
-               animation.duration = 40;
-               /**
-                * Animation types (none,fade,pop,slide)
-                */
-               animation.types = {};
-               animation.types.fade = [{
-                       x: 0.4,
-                       y: 0.4,
-                       w: 0.6,
-                       h: 0.6,
-                       o: 0.0
-               }, {
-                               x: 0.4,
-                               y: 0.4,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 0.1
-                       }, {
-                               x: 0.4,
-                               y: 0.4,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 0.2
-                       }, {
-                               x: 0.4,
-                               y: 0.4,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 0.3
-                       }, {
-                               x: 0.4,
-                               y: 0.4,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 0.4
-                       }, {
-                               x: 0.4,
-                               y: 0.4,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 0.5
-                       }, {
-                               x: 0.4,
-                               y: 0.4,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 0.6
-                       }, {
-                               x: 0.4,
-                               y: 0.4,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 0.7
-                       }, {
-                               x: 0.4,
-                               y: 0.4,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 0.8
-                       }, {
-                               x: 0.4,
-                               y: 0.4,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 0.9
-                       }, {
-                               x: 0.4,
-                               y: 0.4,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 1.0
-                       }];
-               animation.types.none = [{
-                       x: 0.4,
-                       y: 0.4,
-                       w: 0.6,
-                       h: 0.6,
-                       o: 1
-               }];
-               animation.types.pop = [{
-                       x: 1,
-                       y: 1,
-                       w: 0,
-                       h: 0,
-                       o: 1
-               }, {
-                               x: 0.9,
-                               y: 0.9,
-                               w: 0.1,
-                               h: 0.1,
-                               o: 1
-                       }, {
-                               x: 0.8,
-                               y: 0.8,
-                               w: 0.2,
-                               h: 0.2,
-                               o: 1
-                       }, {
-                               x: 0.7,
-                               y: 0.7,
-                               w: 0.3,
-                               h: 0.3,
-                               o: 1
-                       }, {
-                               x: 0.6,
-                               y: 0.6,
-                               w: 0.4,
-                               h: 0.4,
-                               o: 1
-                       }, {
-                               x: 0.5,
-                               y: 0.5,
-                               w: 0.5,
-                               h: 0.5,
-                               o: 1
-                       }, {
-                               x: 0.4,
-                               y: 0.4,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 1
-                       }];
-               animation.types.popFade = [{
-                       x: 0.75,
-                       y: 0.75,
-                       w: 0,
-                       h: 0,
-                       o: 0
-               }, {
-                               x: 0.65,
-                               y: 0.65,
-                               w: 0.1,
-                               h: 0.1,
-                               o: 0.2
-                       }, {
-                               x: 0.6,
-                               y: 0.6,
-                               w: 0.2,
-                               h: 0.2,
-                               o: 0.4
-                       }, {
-                               x: 0.55,
-                               y: 0.55,
-                               w: 0.3,
-                               h: 0.3,
-                               o: 0.6
-                       }, {
-                               x: 0.50,
-                               y: 0.50,
-                               w: 0.4,
-                               h: 0.4,
-                               o: 0.8
-                       }, {
-                               x: 0.45,
-                               y: 0.45,
-                               w: 0.5,
-                               h: 0.5,
-                               o: 0.9
-                       }, {
-                               x: 0.4,
-                               y: 0.4,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 1
-                       }];
-               animation.types.slide = [{
-                       x: 0.4,
-                       y: 1,
-                       w: 0.6,
-                       h: 0.6,
-                       o: 1
-               }, {
-                               x: 0.4,
-                               y: 0.9,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 1
-                       }, {
-                               x: 0.4,
-                               y: 0.9,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 1
-                       }, {
-                               x: 0.4,
-                               y: 0.8,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 1
-                       }, {
-                               x: 0.4,
-                               y: 0.7,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 1
-                       }, {
-                               x: 0.4,
-                               y: 0.6,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 1
-                       }, {
-                               x: 0.4,
-                               y: 0.5,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 1
-                       }, {
-                               x: 0.4,
-                               y: 0.4,
-                               w: 0.6,
-                               h: 0.6,
-                               o: 1
-                       }];
-               /**
-                * Run animation
-                * @param {Object} opt Animation options
-                * @param {Object} cb Callabak after all steps are done
-                * @param {Object} revert Reverse order? true|false
-                * @param {Object} step Optional step number (frame bumber)
-                */
-               animation.run = function (opt, cb, revert, step) {
-                       var animationType = animation.types[isPageHidden() ? 'none' : _opt.animation];
-                       if (revert === true) {
-                               step = (typeof step !== 'undefined') ? step : animationType.length - 1;
-                       } else {
-                               step = (typeof step !== 'undefined') ? step : 0;
-                       }
-                       cb = (cb) ? cb : function () {
-                       };
-                       if ((step < animationType.length) && (step >= 0)) {
-                               type[_opt.type](merge(opt, animationType[step]));
-                               _animTimeout = setTimeout(function () {
-                                       if (revert) {
-                                               step = step - 1;
-                                       } else {
-                                               step = step + 1;
-                                       }
-                                       animation.run(opt, cb, revert, step);
-                               }, animation.duration);
-
-                               link.setIcon(_canvas);
-                       } else {
-                               cb();
-                               return;
-                       }
-               };
-               //auto init
-               init();
-               return {
-                       badge: badge,
-                       video: video,
-                       image: image,
-                       rawImageSrc: rawImageSrc,
-                       webcam: webcam,
-                       setOpt: setOpt,
-                       reset: icon.reset,
-                       browser: {
-                               supported: _browser.supported
-                       }
-               };
-       });
-
-       // AMD / RequireJS
-       if (typeof define !== 'undefined' && define.amd) {
-               define('favico',[], function () {
-                       return Favico;
-               });
-       }
-       // CommonJS
-       else if (typeof module !== 'undefined' && module.exports) {
-               module.exports = Favico;
-       }
-       // included directly via <script> tag
-       else {
-               this.Favico = Favico;
-       }
-
-})();
-
-/*!
- * enquire.js v2.1.2 - Awesome Media Queries in JavaScript
- * Copyright (c) 2014 Nick Williams - http://wicky.nillia.ms/enquire.js
- * License: MIT (http://www.opensource.org/licenses/mit-license.php)
- */
-
-;(function (name, context, factory) {
-       var matchMedia = window.matchMedia;
-
-       if (typeof module !== 'undefined' && module.exports) {
-               module.exports = factory(matchMedia);
-       }
-       else if (typeof define === 'function' && define.amd) {
-               define('enquire',[],function() {
-                       return (context[name] = factory(matchMedia));
-               });
-       }
-       else {
-               context[name] = factory(matchMedia);
-       }
-}('enquire', this, function (matchMedia) {
-
-       'use strict';
-
-    /*jshint unused:false */
-    /**
-     * Helper function for iterating over a collection
-     *
-     * @param collection
-     * @param fn
-     */
-    function each(collection, fn) {
-        var i      = 0,
-            length = collection.length,
-            cont;
-
-        for(i; i < length; i++) {
-            cont = fn(collection[i], i);
-            if(cont === false) {
-                break; //allow early exit
-            }
-        }
-    }
-
-    /**
-     * Helper function for determining whether target object is an array
-     *
-     * @param target the object under test
-     * @return {Boolean} true if array, false otherwise
-     */
-    function isArray(target) {
-        return Object.prototype.toString.apply(target) === '[object Array]';
-    }
-
-    /**
-     * Helper function for determining whether target object is a function
-     *
-     * @param target the object under test
-     * @return {Boolean} true if function, false otherwise
-     */
-    function isFunction(target) {
-        return typeof target === 'function';
-    }
-
-    /**
-     * Delegate to handle a media query being matched and unmatched.
-     *
-     * @param {object} options
-     * @param {function} options.match callback for when the media query is matched
-     * @param {function} [options.unmatch] callback for when the media query is unmatched
-     * @param {function} [options.setup] one-time callback triggered the first time a query is matched
-     * @param {boolean} [options.deferSetup=false] should the setup callback be run immediately, rather than first time query is matched?
-     * @constructor
-     */
-    function QueryHandler(options) {
-        this.options = options;
-        !options.deferSetup && this.setup();
-    }
-    QueryHandler.prototype = {
-
-        /**
-         * coordinates setup of the handler
-         *
-         * @function
-         */
-        setup : function() {
-            if(this.options.setup) {
-                this.options.setup();
-            }
-            this.initialised = true;
-        },
-
-        /**
-         * coordinates setup and triggering of the handler
-         *
-         * @function
-         */
-        on : function() {
-            !this.initialised && this.setup();
-            this.options.match && this.options.match();
-        },
-
-        /**
-         * coordinates the unmatch event for the handler
-         *
-         * @function
-         */
-        off : function() {
-            this.options.unmatch && this.options.unmatch();
-        },
-
-        /**
-         * called when a handler is to be destroyed.
-         * delegates to the destroy or unmatch callbacks, depending on availability.
-         *
-         * @function
-         */
-        destroy : function() {
-            this.options.destroy ? this.options.destroy() : this.off();
-        },
-
-        /**
-         * determines equality by reference.
-         * if object is supplied compare options, if function, compare match callback
-         *
-         * @function
-         * @param {object || function} [target] the target for comparison
-         */
-        equals : function(target) {
-            return this.options === target || this.options.match === target;
-        }
-
-    };
-    /**
-     * Represents a single media query, manages it's state and registered handlers for this query
-     *
-     * @constructor
-     * @param {string} query the media query string
-     * @param {boolean} [isUnconditional=false] whether the media query should run regardless of whether the conditions are met. Primarily for helping older browsers deal with mobile-first design
-     */
-    function MediaQuery(query, isUnconditional) {
-        this.query = query;
-        this.isUnconditional = isUnconditional;
-        this.handlers = [];
-        this.mql = matchMedia(query);
-
-        var self = this;
-        this.listener = function(mql) {
-            self.mql = mql;
-            self.assess();
-        };
-        this.mql.addListener(this.listener);
-    }
-    MediaQuery.prototype = {
-
-        /**
-         * add a handler for this query, triggering if already active
-         *
-         * @param {object} handler
-         * @param {function} handler.match callback for when query is activated
-         * @param {function} [handler.unmatch] callback for when query is deactivated
-         * @param {function} [handler.setup] callback for immediate execution when a query handler is registered
-         * @param {boolean} [handler.deferSetup=false] should the setup callback be deferred until the first time the handler is matched?
-         */
-        addHandler : function(handler) {
-            var qh = new QueryHandler(handler);
-            this.handlers.push(qh);
-
-            this.matches() && qh.on();
-        },
-
-        /**
-         * removes the given handler from the collection, and calls it's destroy methods
-         * 
-         * @param {object || function} handler the handler to remove
-         */
-        removeHandler : function(handler) {
-            var handlers = this.handlers;
-            each(handlers, function(h, i) {
-                if(h.equals(handler)) {
-                    h.destroy();
-                    return !handlers.splice(i,1); //remove from array and exit each early
-                }
-            });
-        },
-
-        /**
-         * Determine whether the media query should be considered a match
-         * 
-         * @return {Boolean} true if media query can be considered a match, false otherwise
-         */
-        matches : function() {
-            return this.mql.matches || this.isUnconditional;
-        },
-
-        /**
-         * Clears all handlers and unbinds events
-         */
-        clear : function() {
-            each(this.handlers, function(handler) {
-                handler.destroy();
-            });
-            this.mql.removeListener(this.listener);
-            this.handlers.length = 0; //clear array
-        },
-
-        /*
-         * Assesses the query, turning on all handlers if it matches, turning them off if it doesn't match
-         */
-        assess : function() {
-            var action = this.matches() ? 'on' : 'off';
-
-            each(this.handlers, function(handler) {
-                handler[action]();
-            });
-        }
-    };
-    /**
-     * Allows for registration of query handlers.
-     * Manages the query handler's state and is responsible for wiring up browser events
-     *
-     * @constructor
-     */
-    function MediaQueryDispatch () {
-        if(!matchMedia) {
-            throw new Error('matchMedia not present, legacy browsers require a polyfill');
-        }
-
-        this.queries = {};
-        this.browserIsIncapable = !matchMedia('only all').matches;
-    }
-
-    MediaQueryDispatch.prototype = {
-
-        /**
-         * Registers a handler for the given media query
-         *
-         * @param {string} q the media query
-         * @param {object || Array || Function} options either a single query handler object, a function, or an array of query handlers
-         * @param {function} options.match fired when query matched
-         * @param {function} [options.unmatch] fired when a query is no longer matched
-         * @param {function} [options.setup] fired when handler first triggered
-         * @param {boolean} [options.deferSetup=false] whether setup should be run immediately or deferred until query is first matched
-         * @param {boolean} [shouldDegrade=false] whether this particular media query should always run on incapable browsers
-         */
-        register : function(q, options, shouldDegrade) {
-            var queries         = this.queries,
-                isUnconditional = shouldDegrade && this.browserIsIncapable;
-
-            if(!queries[q]) {
-                queries[q] = new MediaQuery(q, isUnconditional);
-            }
-
-            //normalise to object in an array
-            if(isFunction(options)) {
-                options = { match : options };
-            }
-            if(!isArray(options)) {
-                options = [options];
-            }
-            each(options, function(handler) {
-                if (isFunction(handler)) {
-                    handler = { match : handler };
-                }
-                queries[q].addHandler(handler);
-            });
-
-            return this;
-        },
-
-        /**
-         * unregisters a query and all it's handlers, or a specific handler for a query
-         *
-         * @param {string} q the media query to target
-         * @param {object || function} [handler] specific handler to unregister
-         */
-        unregister : function(q, handler) {
-            var query = this.queries[q];
-
-            if(query) {
-                if(handler) {
-                    query.removeHandler(handler);
-                }
-                else {
-                    query.clear();
-                    delete this.queries[q];
-                }
-            }
-
-            return this;
-        }
-    };
-
-       return new MediaQueryDispatch();
-
-}));
-/* perfect-scrollbar v0.6.16 */
-(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
-'use strict';
-
-var ps = require('../main');
-
-if (typeof define === 'function' && define.amd) {
-  // AMD
-  define('perfect-scrollbar',ps);
-} else {
-  // Add to a global object.
-  window.PerfectScrollbar = ps;
-  if (typeof window.Ps === 'undefined') {
-    window.Ps = ps;
-  }
-}
-
-},{"../main":7}],2:[function(require,module,exports){
-'use strict';
-
-function oldAdd(element, className) {
-  var classes = element.className.split(' ');
-  if (classes.indexOf(className) < 0) {
-    classes.push(className);
-  }
-  element.className = classes.join(' ');
-}
-
-function oldRemove(element, className) {
-  var classes = element.className.split(' ');
-  var idx = classes.indexOf(className);
-  if (idx >= 0) {
-    classes.splice(idx, 1);
-  }
-  element.className = classes.join(' ');
-}
-
-exports.add = function (element, className) {
-  if (element.classList) {
-    element.classList.add(className);
-  } else {
-    oldAdd(element, className);
-  }
-};
-
-exports.remove = function (element, className) {
-  if (element.classList) {
-    element.classList.remove(className);
-  } else {
-    oldRemove(element, className);
-  }
-};
-
-exports.list = function (element) {
-  if (element.classList) {
-    return Array.prototype.slice.apply(element.classList);
-  } else {
-    return element.className.split(' ');
-  }
-};
-
-},{}],3:[function(require,module,exports){
-'use strict';
-
-var DOM = {};
-
-DOM.e = function (tagName, className) {
-  var element = document.createElement(tagName);
-  element.className = className;
-  return element;
-};
-
-DOM.appendTo = function (child, parent) {
-  parent.appendChild(child);
-  return child;
-};
-
-function cssGet(element, styleName) {
-  return window.getComputedStyle(element)[styleName];
-}
-
-function cssSet(element, styleName, styleValue) {
-  if (typeof styleValue === 'number') {
-    styleValue = styleValue.toString() + 'px';
-  }
-  element.style[styleName] = styleValue;
-  return element;
-}
-
-function cssMultiSet(element, obj) {
-  for (var key in obj) {
-    var val = obj[key];
-    if (typeof val === 'number') {
-      val = val.toString() + 'px';
-    }
-    element.style[key] = val;
-  }
-  return element;
-}
-
-DOM.css = function (element, styleNameOrObject, styleValue) {
-  if (typeof styleNameOrObject === 'object') {
-    // multiple set with object
-    return cssMultiSet(element, styleNameOrObject);
-  } else {
-    if (typeof styleValue === 'undefined') {
-      return cssGet(element, styleNameOrObject);
-    } else {
-      return cssSet(element, styleNameOrObject, styleValue);
-    }
-  }
-};
-
-DOM.matches = function (element, query) {
-  if (typeof element.matches !== 'undefined') {
-    return element.matches(query);
-  } else {
-    if (typeof element.matchesSelector !== 'undefined') {
-      return element.matchesSelector(query);
-    } else if (typeof element.webkitMatchesSelector !== 'undefined') {
-      return element.webkitMatchesSelector(query);
-    } else if (typeof element.mozMatchesSelector !== 'undefined') {
-      return element.mozMatchesSelector(query);
-    } else if (typeof element.msMatchesSelector !== 'undefined') {
-      return element.msMatchesSelector(query);
-    }
-  }
-};
-
-DOM.remove = function (element) {
-  if (typeof element.remove !== 'undefined') {
-    element.remove();
-  } else {
-    if (element.parentNode) {
-      element.parentNode.removeChild(element);
-    }
-  }
-};
-
-DOM.queryChildren = function (element, selector) {
-  return Array.prototype.filter.call(element.childNodes, function (child) {
-    return DOM.matches(child, selector);
-  });
-};
-
-module.exports = DOM;
-
-},{}],4:[function(require,module,exports){
-'use strict';
-
-var EventElement = function (element) {
-  this.element = element;
-  this.events = {};
-};
-
-EventElement.prototype.bind = function (eventName, handler) {
-  if (typeof this.events[eventName] === 'undefined') {
-    this.events[eventName] = [];
-  }
-  this.events[eventName].push(handler);
-  this.element.addEventListener(eventName, handler, false);
-};
-
-EventElement.prototype.unbind = function (eventName, handler) {
-  var isHandlerProvided = (typeof handler !== 'undefined');
-  this.events[eventName] = this.events[eventName].filter(function (hdlr) {
-    if (isHandlerProvided && hdlr !== handler) {
-      return true;
-    }
-    this.element.removeEventListener(eventName, hdlr, false);
-    return false;
-  }, this);
-};
-
-EventElement.prototype.unbindAll = function () {
-  for (var name in this.events) {
-    this.unbind(name);
-  }
-};
-
-var EventManager = function () {
-  this.eventElements = [];
-};
-
-EventManager.prototype.eventElement = function (element) {
-  var ee = this.eventElements.filter(function (eventElement) {
-    return eventElement.element === element;
-  })[0];
-  if (typeof ee === 'undefined') {
-    ee = new EventElement(element);
-    this.eventElements.push(ee);
-  }
-  return ee;
-};
-
-EventManager.prototype.bind = function (element, eventName, handler) {
-  this.eventElement(element).bind(eventName, handler);
-};
-
-EventManager.prototype.unbind = function (element, eventName, handler) {
-  this.eventElement(element).unbind(eventName, handler);
-};
-
-EventManager.prototype.unbindAll = function () {
-  for (var i = 0; i < this.eventElements.length; i++) {
-    this.eventElements[i].unbindAll();
-  }
-};
-
-EventManager.prototype.once = function (element, eventName, handler) {
-  var ee = this.eventElement(element);
-  var onceHandler = function (e) {
-    ee.unbind(eventName, onceHandler);
-    handler(e);
-  };
-  ee.bind(eventName, onceHandler);
-};
-
-module.exports = EventManager;
-
-},{}],5:[function(require,module,exports){
-'use strict';
-
-module.exports = (function () {
-  function s4() {
-    return Math.floor((1 + Math.random()) * 0x10000)
-               .toString(16)
-               .substring(1);
-  }
-  return function () {
-    return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
-           s4() + '-' + s4() + s4() + s4();
-  };
-})();
-
-},{}],6:[function(require,module,exports){
-'use strict';
-
-var cls = require('./class');
-var dom = require('./dom');
-
-var toInt = exports.toInt = function (x) {
-  return parseInt(x, 10) || 0;
-};
-
-var clone = exports.clone = function (obj) {
-  if (!obj) {
-    return null;
-  } else if (obj.constructor === Array) {
-    return obj.map(clone);
-  } else if (typeof obj === 'object') {
-    var result = {};
-    for (var key in obj) {
-      result[key] = clone(obj[key]);
-    }
-    return result;
-  } else {
-    return obj;
-  }
-};
-
-exports.extend = function (original, source) {
-  var result = clone(original);
-  for (var key in source) {
-    result[key] = clone(source[key]);
-  }
-  return result;
-};
-
-exports.isEditable = function (el) {
-  return dom.matches(el, "input,[contenteditable]") ||
-         dom.matches(el, "select,[contenteditable]") ||
-         dom.matches(el, "textarea,[contenteditable]") ||
-         dom.matches(el, "button,[contenteditable]");
-};
-
-exports.removePsClasses = function (element) {
-  var clsList = cls.list(element);
-  for (var i = 0; i < clsList.length; i++) {
-    var className = clsList[i];
-    if (className.indexOf('ps-') === 0) {
-      cls.remove(element, className);
-    }
-  }
-};
-
-exports.outerWidth = function (element) {
-  return toInt(dom.css(element, 'width')) +
-         toInt(dom.css(element, 'paddingLeft')) +
-         toInt(dom.css(element, 'paddingRight')) +
-         toInt(dom.css(element, 'borderLeftWidth')) +
-         toInt(dom.css(element, 'borderRightWidth'));
-};
-
-exports.startScrolling = function (element, axis) {
-  cls.add(element, 'ps-in-scrolling');
-  if (typeof axis !== 'undefined') {
-    cls.add(element, 'ps-' + axis);
-  } else {
-    cls.add(element, 'ps-x');
-    cls.add(element, 'ps-y');
-  }
-};
-
-exports.stopScrolling = function (element, axis) {
-  cls.remove(element, 'ps-in-scrolling');
-  if (typeof axis !== 'undefined') {
-    cls.remove(element, 'ps-' + axis);
-  } else {
-    cls.remove(element, 'ps-x');
-    cls.remove(element, 'ps-y');
-  }
-};
-
-exports.env = {
-  isWebKit: 'WebkitAppearance' in document.documentElement.style,
-  supportsTouch: (('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch),
-  supportsIePointer: window.navigator.msMaxTouchPoints !== null
-};
-
-},{"./class":2,"./dom":3}],7:[function(require,module,exports){
-'use strict';
-
-var destroy = require('./plugin/destroy');
-var initialize = require('./plugin/initialize');
-var update = require('./plugin/update');
-
-module.exports = {
-  initialize: initialize,
-  update: update,
-  destroy: destroy
-};
-
-},{"./plugin/destroy":9,"./plugin/initialize":17,"./plugin/update":21}],8:[function(require,module,exports){
-'use strict';
-
-module.exports = {
-  handlers: ['click-rail', 'drag-scrollbar', 'keyboard', 'wheel', 'touch'],
-  maxScrollbarLength: null,
-  minScrollbarLength: null,
-  scrollXMarginOffset: 0,
-  scrollYMarginOffset: 0,
-  suppressScrollX: false,
-  suppressScrollY: false,
-  swipePropagation: true,
-  useBothWheelAxes: false,
-  wheelPropagation: false,
-  wheelSpeed: 1,
-  theme: 'default'
-};
-
-},{}],9:[function(require,module,exports){
-'use strict';
-
-var _ = require('../lib/helper');
-var dom = require('../lib/dom');
-var instances = require('./instances');
-
-module.exports = function (element) {
-  var i = instances.get(element);
-
-  if (!i) {
-    return;
-  }
-
-  i.event.unbindAll();
-  dom.remove(i.scrollbarX);
-  dom.remove(i.scrollbarY);
-  dom.remove(i.scrollbarXRail);
-  dom.remove(i.scrollbarYRail);
-  _.removePsClasses(element);
-
-  instances.remove(element);
-};
-
-},{"../lib/dom":3,"../lib/helper":6,"./instances":18}],10:[function(require,module,exports){
-'use strict';
-
-var instances = require('../instances');
-var updateGeometry = require('../update-geometry');
-var updateScroll = require('../update-scroll');
-
-function bindClickRailHandler(element, i) {
-  function pageOffset(el) {
-    return el.getBoundingClientRect();
-  }
-  var stopPropagation = function (e) { e.stopPropagation(); };
-
-  i.event.bind(i.scrollbarY, 'click', stopPropagation);
-  i.event.bind(i.scrollbarYRail, 'click', function (e) {
-    var positionTop = e.pageY - window.pageYOffset - pageOffset(i.scrollbarYRail).top;
-    var direction = positionTop > i.scrollbarYTop ? 1 : -1;
-
-    updateScroll(element, 'top', element.scrollTop + direction * i.containerHeight);
-    updateGeometry(element);
-
-    e.stopPropagation();
-  });
-
-  i.event.bind(i.scrollbarX, 'click', stopPropagation);
-  i.event.bind(i.scrollbarXRail, 'click', function (e) {
-    var positionLeft = e.pageX - window.pageXOffset - pageOffset(i.scrollbarXRail).left;
-    var direction = positionLeft > i.scrollbarXLeft ? 1 : -1;
-
-    updateScroll(element, 'left', element.scrollLeft + direction * i.containerWidth);
-    updateGeometry(element);
-
-    e.stopPropagation();
-  });
-}
-
-module.exports = function (element) {
-  var i = instances.get(element);
-  bindClickRailHandler(element, i);
-};
-
-},{"../instances":18,"../update-geometry":19,"../update-scroll":20}],11:[function(require,module,exports){
-'use strict';
-
-var _ = require('../../lib/helper');
-var dom = require('../../lib/dom');
-var instances = require('../instances');
-var updateGeometry = require('../update-geometry');
-var updateScroll = require('../update-scroll');
-
-function bindMouseScrollXHandler(element, i) {
-  var currentLeft = null;
-  var currentPageX = null;
-
-  function updateScrollLeft(deltaX) {
-    var newLeft = currentLeft + (deltaX * i.railXRatio);
-    var maxLeft = Math.max(0, i.scrollbarXRail.getBoundingClientRect().left) + (i.railXRatio * (i.railXWidth - i.scrollbarXWidth));
-
-    if (newLeft < 0) {
-      i.scrollbarXLeft = 0;
-    } else if (newLeft > maxLeft) {
-      i.scrollbarXLeft = maxLeft;
-    } else {
-      i.scrollbarXLeft = newLeft;
-    }
-
-    var scrollLeft = _.toInt(i.scrollbarXLeft * (i.contentWidth - i.containerWidth) / (i.containerWidth - (i.railXRatio * i.scrollbarXWidth))) - i.negativeScrollAdjustment;
-    updateScroll(element, 'left', scrollLeft);
-  }
-
-  var mouseMoveHandler = function (e) {
-    updateScrollLeft(e.pageX - currentPageX);
-    updateGeometry(element);
-    e.stopPropagation();
-    e.preventDefault();
-  };
-
-  var mouseUpHandler = function () {
-    _.stopScrolling(element, 'x');
-    i.event.unbind(i.ownerDocument, 'mousemove', mouseMoveHandler);
-  };
-
-  i.event.bind(i.scrollbarX, 'mousedown', function (e) {
-    currentPageX = e.pageX;
-    currentLeft = _.toInt(dom.css(i.scrollbarX, 'left')) * i.railXRatio;
-    _.startScrolling(element, 'x');
-
-    i.event.bind(i.ownerDocument, 'mousemove', mouseMoveHandler);
-    i.event.once(i.ownerDocument, 'mouseup', mouseUpHandler);
-
-    e.stopPropagation();
-    e.preventDefault();
-  });
-}
-
-function bindMouseScrollYHandler(element, i) {
-  var currentTop = null;
-  var currentPageY = null;
-
-  function updateScrollTop(deltaY) {
-    var newTop = currentTop + (deltaY * i.railYRatio);
-    var maxTop = Math.max(0, i.scrollbarYRail.getBoundingClientRect().top) + (i.railYRatio * (i.railYHeight - i.scrollbarYHeight));
-
-    if (newTop < 0) {
-      i.scrollbarYTop = 0;
-    } else if (newTop > maxTop) {
-      i.scrollbarYTop = maxTop;
-    } else {
-      i.scrollbarYTop = newTop;
-    }
-
-    var scrollTop = _.toInt(i.scrollbarYTop * (i.contentHeight - i.containerHeight) / (i.containerHeight - (i.railYRatio * i.scrollbarYHeight)));
-    updateScroll(element, 'top', scrollTop);
-  }
-
-  var mouseMoveHandler = function (e) {
-    updateScrollTop(e.pageY - currentPageY);
-    updateGeometry(element);
-    e.stopPropagation();
-    e.preventDefault();
-  };
-
-  var mouseUpHandler = function () {
-    _.stopScrolling(element, 'y');
-    i.event.unbind(i.ownerDocument, 'mousemove', mouseMoveHandler);
-  };
-
-  i.event.bind(i.scrollbarY, 'mousedown', function (e) {
-    currentPageY = e.pageY;
-    currentTop = _.toInt(dom.css(i.scrollbarY, 'top')) * i.railYRatio;
-    _.startScrolling(element, 'y');
-
-    i.event.bind(i.ownerDocument, 'mousemove', mouseMoveHandler);
-    i.event.once(i.ownerDocument, 'mouseup', mouseUpHandler);
-
-    e.stopPropagation();
-    e.preventDefault();
-  });
-}
-
-module.exports = function (element) {
-  var i = instances.get(element);
-  bindMouseScrollXHandler(element, i);
-  bindMouseScrollYHandler(element, i);
-};
-
-},{"../../lib/dom":3,"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],12:[function(require,module,exports){
-'use strict';
-
-var _ = require('../../lib/helper');
-var dom = require('../../lib/dom');
-var instances = require('../instances');
-var updateGeometry = require('../update-geometry');
-var updateScroll = require('../update-scroll');
-
-function bindKeyboardHandler(element, i) {
-  var hovered = false;
-  i.event.bind(element, 'mouseenter', function () {
-    hovered = true;
-  });
-  i.event.bind(element, 'mouseleave', function () {
-    hovered = false;
-  });
-
-  var shouldPrevent = false;
-  function shouldPreventDefault(deltaX, deltaY) {
-    var scrollTop = element.scrollTop;
-    if (deltaX === 0) {
-      if (!i.scrollbarYActive) {
-        return false;
-      }
-      if ((scrollTop === 0 && deltaY > 0) || (scrollTop >= i.contentHeight - i.containerHeight && deltaY < 0)) {
-        return !i.settings.wheelPropagation;
-      }
-    }
-
-    var scrollLeft = element.scrollLeft;
-    if (deltaY === 0) {
-      if (!i.scrollbarXActive) {
-        return false;
-      }
-      if ((scrollLeft === 0 && deltaX < 0) || (scrollLeft >= i.contentWidth - i.containerWidth && deltaX > 0)) {
-        return !i.settings.wheelPropagation;
-      }
-    }
-    return true;
-  }
-
-  i.event.bind(i.ownerDocument, 'keydown', function (e) {
-    if ((e.isDefaultPrevented && e.isDefaultPrevented()) || e.defaultPrevented) {
-      return;
-    }
-
-    var focused = dom.matches(i.scrollbarX, ':focus') ||
-                  dom.matches(i.scrollbarY, ':focus');
-
-    if (!hovered && !focused) {
-      return;
-    }
-
-    var activeElement = document.activeElement ? document.activeElement : i.ownerDocument.activeElement;
-    if (activeElement) {
-      if (activeElement.tagName === 'IFRAME') {
-        activeElement = activeElement.contentDocument.activeElement;
-      } else {
-        // go deeper if element is a webcomponent
-        while (activeElement.shadowRoot) {
-          activeElement = activeElement.shadowRoot.activeElement;
-        }
-      }
-      if (_.isEditable(activeElement)) {
-        return;
-      }
-    }
-
-    var deltaX = 0;
-    var deltaY = 0;
-
-    switch (e.which) {
-    case 37: // left
-      if (e.metaKey) {
-        deltaX = -i.contentWidth;
-      } else if (e.altKey) {
-        deltaX = -i.containerWidth;
-      } else {
-        deltaX = -30;
-      }
-      break;
-    case 38: // up
-      if (e.metaKey) {
-        deltaY = i.contentHeight;
-      } else if (e.altKey) {
-        deltaY = i.containerHeight;
-      } else {
-        deltaY = 30;
-      }
-      break;
-    case 39: // right
-      if (e.metaKey) {
-        deltaX = i.contentWidth;
-      } else if (e.altKey) {
-        deltaX = i.containerWidth;
-      } else {
-        deltaX = 30;
-      }
-      break;
-    case 40: // down
-      if (e.metaKey) {
-        deltaY = -i.contentHeight;
-      } else if (e.altKey) {
-        deltaY = -i.containerHeight;
-      } else {
-        deltaY = -30;
-      }
-      break;
-    case 33: // page up
-      deltaY = 90;
-      break;
-    case 32: // space bar
-      if (e.shiftKey) {
-        deltaY = 90;
-      } else {
-        deltaY = -90;
-      }
-      break;
-    case 34: // page down
-      deltaY = -90;
-      break;
-    case 35: // end
-      if (e.ctrlKey) {
-        deltaY = -i.contentHeight;
-      } else {
-        deltaY = -i.containerHeight;
-      }
-      break;
-    case 36: // home
-      if (e.ctrlKey) {
-        deltaY = element.scrollTop;
-      } else {
-        deltaY = i.containerHeight;
-      }
-      break;
-    default:
-      return;
-    }
-
-    updateScroll(element, 'top', element.scrollTop - deltaY);
-    updateScroll(element, 'left', element.scrollLeft + deltaX);
-    updateGeometry(element);
-
-    shouldPrevent = shouldPreventDefault(deltaX, deltaY);
-    if (shouldPrevent) {
-      e.preventDefault();
-    }
-  });
-}
-
-module.exports = function (element) {
-  var i = instances.get(element);
-  bindKeyboardHandler(element, i);
-};
-
-},{"../../lib/dom":3,"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],13:[function(require,module,exports){
-'use strict';
-
-var instances = require('../instances');
-var updateGeometry = require('../update-geometry');
-var updateScroll = require('../update-scroll');
-
-function bindMouseWheelHandler(element, i) {
-  var shouldPrevent = false;
-
-  function shouldPreventDefault(deltaX, deltaY) {
-    var scrollTop = element.scrollTop;
-    if (deltaX === 0) {
-      if (!i.scrollbarYActive) {
-        return false;
-      }
-      if ((scrollTop === 0 && deltaY > 0) || (scrollTop >= i.contentHeight - i.containerHeight && deltaY < 0)) {
-        return !i.settings.wheelPropagation;
-      }
-    }
-
-    var scrollLeft = element.scrollLeft;
-    if (deltaY === 0) {
-      if (!i.scrollbarXActive) {
-        return false;
-      }
-      if ((scrollLeft === 0 && deltaX < 0) || (scrollLeft >= i.contentWidth - i.containerWidth && deltaX > 0)) {
-        return !i.settings.wheelPropagation;
-      }
-    }
-    return true;
-  }
-
-  function getDeltaFromEvent(e) {
-    var deltaX = e.deltaX;
-    var deltaY = -1 * e.deltaY;
-
-    if (typeof deltaX === "undefined" || typeof deltaY === "undefined") {
-      // OS X Safari
-      deltaX = -1 * e.wheelDeltaX / 6;
-      deltaY = e.wheelDeltaY / 6;
-    }
-
-    if (e.deltaMode && e.deltaMode === 1) {
-      // Firefox in deltaMode 1: Line scrolling
-      deltaX *= 10;
-      deltaY *= 10;
-    }
-
-    if (deltaX !== deltaX && deltaY !== deltaY/* NaN checks */) {
-      // IE in some mouse drivers
-      deltaX = 0;
-      deltaY = e.wheelDelta;
-    }
-
-    if (e.shiftKey) {
-      // reverse axis with shift key
-      return [-deltaY, -deltaX];
-    }
-    return [deltaX, deltaY];
-  }
-
-  function shouldBeConsumedByChild(deltaX, deltaY) {
-    var child = element.querySelector('textarea:hover, select[multiple]:hover, .ps-child:hover');
-    if (child) {
-      if (!window.getComputedStyle(child).overflow.match(/(scroll|auto)/)) {
-        // if not scrollable
-        return false;
-      }
-
-      var maxScrollTop = child.scrollHeight - child.clientHeight;
-      if (maxScrollTop > 0) {
-        if (!(child.scrollTop === 0 && deltaY > 0) && !(child.scrollTop === maxScrollTop && deltaY < 0)) {
-          return true;
-        }
-      }
-      var maxScrollLeft = child.scrollLeft - child.clientWidth;
-      if (maxScrollLeft > 0) {
-        if (!(child.scrollLeft === 0 && deltaX < 0) && !(child.scrollLeft === maxScrollLeft && deltaX > 0)) {
-          return true;
-        }
-      }
-    }
-    return false;
-  }
-
-  function mousewheelHandler(e) {
-    var delta = getDeltaFromEvent(e);
-
-    var deltaX = delta[0];
-    var deltaY = delta[1];
-
-    if (shouldBeConsumedByChild(deltaX, deltaY)) {
-      return;
-    }
-
-    shouldPrevent = false;
-    if (!i.settings.useBothWheelAxes) {
-      // deltaX will only be used for horizontal scrolling and deltaY will
-      // only be used for vertical scrolling - this is the default
-      updateScroll(element, 'top', element.scrollTop - (deltaY * i.settings.wheelSpeed));
-      updateScroll(element, 'left', element.scrollLeft + (deltaX * i.settings.wheelSpeed));
-    } else if (i.scrollbarYActive && !i.scrollbarXActive) {
-      // only vertical scrollbar is active and useBothWheelAxes option is
-      // active, so let's scroll vertical bar using both mouse wheel axes
-      if (deltaY) {
-        updateScroll(element, 'top', element.scrollTop - (deltaY * i.settings.wheelSpeed));
-      } else {
-        updateScroll(element, 'top', element.scrollTop + (deltaX * i.settings.wheelSpeed));
-      }
-      shouldPrevent = true;
-    } else if (i.scrollbarXActive && !i.scrollbarYActive) {
-      // useBothWheelAxes and only horizontal bar is active, so use both
-      // wheel axes for horizontal bar
-      if (deltaX) {
-        updateScroll(element, 'left', element.scrollLeft + (deltaX * i.settings.wheelSpeed));
-      } else {
-        updateScroll(element, 'left', element.scrollLeft - (deltaY * i.settings.wheelSpeed));
-      }
-      shouldPrevent = true;
-    }
-
-    updateGeometry(element);
-
-    shouldPrevent = (shouldPrevent || shouldPreventDefault(deltaX, deltaY));
-    if (shouldPrevent) {
-      e.stopPropagation();
-      e.preventDefault();
-    }
-  }
-
-  if (typeof window.onwheel !== "undefined") {
-    i.event.bind(element, 'wheel', mousewheelHandler);
-  } else if (typeof window.onmousewheel !== "undefined") {
-    i.event.bind(element, 'mousewheel', mousewheelHandler);
-  }
-}
-
-module.exports = function (element) {
-  var i = instances.get(element);
-  bindMouseWheelHandler(element, i);
-};
-
-},{"../instances":18,"../update-geometry":19,"../update-scroll":20}],14:[function(require,module,exports){
-'use strict';
-
-var instances = require('../instances');
-var updateGeometry = require('../update-geometry');
-
-function bindNativeScrollHandler(element, i) {
-  i.event.bind(element, 'scroll', function () {
-    updateGeometry(element);
-  });
-}
-
-module.exports = function (element) {
-  var i = instances.get(element);
-  bindNativeScrollHandler(element, i);
-};
-
-},{"../instances":18,"../update-geometry":19}],15:[function(require,module,exports){
-'use strict';
-
-var _ = require('../../lib/helper');
-var instances = require('../instances');
-var updateGeometry = require('../update-geometry');
-var updateScroll = require('../update-scroll');
-
-function bindSelectionHandler(element, i) {
-  function getRangeNode() {
-    var selection = window.getSelection ? window.getSelection() :
-                    document.getSelection ? document.getSelection() : '';
-    if (selection.toString().length === 0) {
-      return null;
-    } else {
-      return selection.getRangeAt(0).commonAncestorContainer;
-    }
-  }
-
-  var scrollingLoop = null;
-  var scrollDiff = {top: 0, left: 0};
-  function startScrolling() {
-    if (!scrollingLoop) {
-      scrollingLoop = setInterval(function () {
-        if (!instances.get(element)) {
-          clearInterval(scrollingLoop);
-          return;
-        }
-
-        updateScroll(element, 'top', element.scrollTop + scrollDiff.top);
-        updateScroll(element, 'left', element.scrollLeft + scrollDiff.left);
-        updateGeometry(element);
-      }, 50); // every .1 sec
-    }
-  }
-  function stopScrolling() {
-    if (scrollingLoop) {
-      clearInterval(scrollingLoop);
-      scrollingLoop = null;
-    }
-    _.stopScrolling(element);
-  }
-
-  var isSelected = false;
-  i.event.bind(i.ownerDocument, 'selectionchange', function () {
-    if (element.contains(getRangeNode())) {
-      isSelected = true;
-    } else {
-      isSelected = false;
-      stopScrolling();
-    }
-  });
-  i.event.bind(window, 'mouseup', function () {
-    if (isSelected) {
-      isSelected = false;
-      stopScrolling();
-    }
-  });
-  i.event.bind(window, 'keyup', function () {
-    if (isSelected) {
-      isSelected = false;
-      stopScrolling();
-    }
-  });
-
-  i.event.bind(window, 'mousemove', function (e) {
-    if (isSelected) {
-      var mousePosition = {x: e.pageX, y: e.pageY};
-      var containerGeometry = {
-        left: element.offsetLeft,
-        right: element.offsetLeft + element.offsetWidth,
-        top: element.offsetTop,
-        bottom: element.offsetTop + element.offsetHeight
-      };
-
-      if (mousePosition.x < containerGeometry.left + 3) {
-        scrollDiff.left = -5;
-        _.startScrolling(element, 'x');
-      } else if (mousePosition.x > containerGeometry.right - 3) {
-        scrollDiff.left = 5;
-        _.startScrolling(element, 'x');
-      } else {
-        scrollDiff.left = 0;
-      }
-
-      if (mousePosition.y < containerGeometry.top + 3) {
-        if (containerGeometry.top + 3 - mousePosition.y < 5) {
-          scrollDiff.top = -5;
-        } else {
-          scrollDiff.top = -20;
-        }
-        _.startScrolling(element, 'y');
-      } else if (mousePosition.y > containerGeometry.bottom - 3) {
-        if (mousePosition.y - containerGeometry.bottom + 3 < 5) {
-          scrollDiff.top = 5;
-        } else {
-          scrollDiff.top = 20;
-        }
-        _.startScrolling(element, 'y');
-      } else {
-        scrollDiff.top = 0;
-      }
-
-      if (scrollDiff.top === 0 && scrollDiff.left === 0) {
-        stopScrolling();
-      } else {
-        startScrolling();
-      }
-    }
-  });
-}
-
-module.exports = function (element) {
-  var i = instances.get(element);
-  bindSelectionHandler(element, i);
-};
-
-},{"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],16:[function(require,module,exports){
-'use strict';
-
-var _ = require('../../lib/helper');
-var instances = require('../instances');
-var updateGeometry = require('../update-geometry');
-var updateScroll = require('../update-scroll');
-
-function bindTouchHandler(element, i, supportsTouch, supportsIePointer) {
-  function shouldPreventDefault(deltaX, deltaY) {
-    var scrollTop = element.scrollTop;
-    var scrollLeft = element.scrollLeft;
-    var magnitudeX = Math.abs(deltaX);
-    var magnitudeY = Math.abs(deltaY);
-
-    if (magnitudeY > magnitudeX) {
-      // user is perhaps trying to swipe up/down the page
-
-      if (((deltaY < 0) && (scrollTop === i.contentHeight - i.containerHeight)) ||
-          ((deltaY > 0) && (scrollTop === 0))) {
-        return !i.settings.swipePropagation;
-      }
-    } else if (magnitudeX > magnitudeY) {
-      // user is perhaps trying to swipe left/right across the page
-
-      if (((deltaX < 0) && (scrollLeft === i.contentWidth - i.containerWidth)) ||
-          ((deltaX > 0) && (scrollLeft === 0))) {
-        return !i.settings.swipePropagation;
-      }
-    }
-
-    return true;
-  }
-
-  function applyTouchMove(differenceX, differenceY) {
-    updateScroll(element, 'top', element.scrollTop - differenceY);
-    updateScroll(element, 'left', element.scrollLeft - differenceX);
-
-    updateGeometry(element);
-  }
-
-  var startOffset = {};
-  var startTime = 0;
-  var speed = {};
-  var easingLoop = null;
-  var inGlobalTouch = false;
-  var inLocalTouch = false;
-
-  function globalTouchStart() {
-    inGlobalTouch = true;
-  }
-  function globalTouchEnd() {
-    inGlobalTouch = false;
-  }
-
-  function getTouch(e) {
-    if (e.targetTouches) {
-      return e.targetTouches[0];
-    } else {
-      // Maybe IE pointer
-      return e;
-    }
-  }
-  function shouldHandle(e) {
-    if (e.targetTouches && e.targetTouches.length === 1) {
-      return true;
-    }
-    if (e.pointerType && e.pointerType !== 'mouse' && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
-      return true;
-    }
-    return false;
-  }
-  function touchStart(e) {
-    if (shouldHandle(e)) {
-      inLocalTouch = true;
-
-      var touch = getTouch(e);
-
-      startOffset.pageX = touch.pageX;
-      startOffset.pageY = touch.pageY;
-
-      startTime = (new Date()).getTime();
-
-      if (easingLoop !== null) {
-        clearInterval(easingLoop);
-      }
-
-      e.stopPropagation();
-    }
-  }
-  function touchMove(e) {
-    if (!inLocalTouch && i.settings.swipePropagation) {
-      touchStart(e);
-    }
-    if (!inGlobalTouch && inLocalTouch && shouldHandle(e)) {
-      var touch = getTouch(e);
-
-      var currentOffset = {pageX: touch.pageX, pageY: touch.pageY};
-
-      var differenceX = currentOffset.pageX - startOffset.pageX;
-      var differenceY = currentOffset.pageY - startOffset.pageY;
-
-      applyTouchMove(differenceX, differenceY);
-      startOffset = currentOffset;
-
-      var currentTime = (new Date()).getTime();
-
-      var timeGap = currentTime - startTime;
-      if (timeGap > 0) {
-        speed.x = differenceX / timeGap;
-        speed.y = differenceY / timeGap;
-        startTime = currentTime;
-      }
-
-      if (shouldPreventDefault(differenceX, differenceY)) {
-        e.stopPropagation();
-        e.preventDefault();
-      }
-    }
-  }
-  function touchEnd() {
-    if (!inGlobalTouch && inLocalTouch) {
-      inLocalTouch = false;
-
-      clearInterval(easingLoop);
-      easingLoop = setInterval(function () {
-        if (!instances.get(element)) {
-          clearInterval(easingLoop);
-          return;
-        }
-
-        if (!speed.x && !speed.y) {
-          clearInterval(easingLoop);
-          return;
-        }
-
-        if (Math.abs(speed.x) < 0.01 && Math.abs(speed.y) < 0.01) {
-          clearInterval(easingLoop);
-          return;
-        }
-
-        applyTouchMove(speed.x * 30, speed.y * 30);
-
-        speed.x *= 0.8;
-        speed.y *= 0.8;
-      }, 10);
-    }
-  }
-
-  if (supportsTouch) {
-    i.event.bind(window, 'touchstart', globalTouchStart);
-    i.event.bind(window, 'touchend', globalTouchEnd);
-    i.event.bind(element, 'touchstart', touchStart);
-    i.event.bind(element, 'touchmove', touchMove);
-    i.event.bind(element, 'touchend', touchEnd);
-  } else if (supportsIePointer) {
-    if (window.PointerEvent) {
-      i.event.bind(window, 'pointerdown', globalTouchStart);
-      i.event.bind(window, 'pointerup', globalTouchEnd);
-      i.event.bind(element, 'pointerdown', touchStart);
-      i.event.bind(element, 'pointermove', touchMove);
-      i.event.bind(element, 'pointerup', touchEnd);
-    } else if (window.MSPointerEvent) {
-      i.event.bind(window, 'MSPointerDown', globalTouchStart);
-      i.event.bind(window, 'MSPointerUp', globalTouchEnd);
-      i.event.bind(element, 'MSPointerDown', touchStart);
-      i.event.bind(element, 'MSPointerMove', touchMove);
-      i.event.bind(element, 'MSPointerUp', touchEnd);
-    }
-  }
-}
-
-module.exports = function (element) {
-  if (!_.env.supportsTouch && !_.env.supportsIePointer) {
-    return;
-  }
-
-  var i = instances.get(element);
-  bindTouchHandler(element, i, _.env.supportsTouch, _.env.supportsIePointer);
-};
-
-},{"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],17:[function(require,module,exports){
-'use strict';
-
-var _ = require('../lib/helper');
-var cls = require('../lib/class');
-var instances = require('./instances');
-var updateGeometry = require('./update-geometry');
-
-// Handlers
-var handlers = {
-  'click-rail': require('./handler/click-rail'),
-  'drag-scrollbar': require('./handler/drag-scrollbar'),
-  'keyboard': require('./handler/keyboard'),
-  'wheel': require('./handler/mouse-wheel'),
-  'touch': require('./handler/touch'),
-  'selection': require('./handler/selection')
-};
-var nativeScrollHandler = require('./handler/native-scroll');
-
-module.exports = function (element, userSettings) {
-  userSettings = typeof userSettings === 'object' ? userSettings : {};
-
-  cls.add(element, 'ps-container');
-
-  // Create a plugin instance.
-  var i = instances.add(element);
-
-  i.settings = _.extend(i.settings, userSettings);
-  cls.add(element, 'ps-theme-' + i.settings.theme);
-
-  i.settings.handlers.forEach(function (handlerName) {
-    handlers[handlerName](element);
-  });
-
-  nativeScrollHandler(element);
-
-  updateGeometry(element);
-};
-
-},{"../lib/class":2,"../lib/helper":6,"./handler/click-rail":10,"./handler/drag-scrollbar":11,"./handler/keyboard":12,"./handler/mouse-wheel":13,"./handler/native-scroll":14,"./handler/selection":15,"./handler/touch":16,"./instances":18,"./update-geometry":19}],18:[function(require,module,exports){
-'use strict';
-
-var _ = require('../lib/helper');
-var cls = require('../lib/class');
-var defaultSettings = require('./default-setting');
-var dom = require('../lib/dom');
-var EventManager = require('../lib/event-manager');
-var guid = require('../lib/guid');
-
-var instances = {};
-
-function Instance(element) {
-  var i = this;
-
-  i.settings = _.clone(defaultSettings);
-  i.containerWidth = null;
-  i.containerHeight = null;
-  i.contentWidth = null;
-  i.contentHeight = null;
-
-  i.isRtl = dom.css(element, 'direction') === "rtl";
-  i.isNegativeScroll = (function () {
-    var originalScrollLeft = element.scrollLeft;
-    var result = null;
-    element.scrollLeft = -1;
-    result = element.scrollLeft < 0;
-    element.scrollLeft = originalScrollLeft;
-    return result;
-  })();
-  i.negativeScrollAdjustment = i.isNegativeScroll ? element.scrollWidth - element.clientWidth : 0;
-  i.event = new EventManager();
-  i.ownerDocument = element.ownerDocument || document;
-
-  function focus() {
-    cls.add(element, 'ps-focus');
-  }
-
-  function blur() {
-    cls.remove(element, 'ps-focus');
-  }
-
-  i.scrollbarXRail = dom.appendTo(dom.e('div', 'ps-scrollbar-x-rail'), element);
-  i.scrollbarX = dom.appendTo(dom.e('div', 'ps-scrollbar-x'), i.scrollbarXRail);
-  i.scrollbarX.setAttribute('tabindex', 0);
-  i.event.bind(i.scrollbarX, 'focus', focus);
-  i.event.bind(i.scrollbarX, 'blur', blur);
-  i.scrollbarXActive = null;
-  i.scrollbarXWidth = null;
-  i.scrollbarXLeft = null;
-  i.scrollbarXBottom = _.toInt(dom.css(i.scrollbarXRail, 'bottom'));
-  i.isScrollbarXUsingBottom = i.scrollbarXBottom === i.scrollbarXBottom; // !isNaN
-  i.scrollbarXTop = i.isScrollbarXUsingBottom ? null : _.toInt(dom.css(i.scrollbarXRail, 'top'));
-  i.railBorderXWidth = _.toInt(dom.css(i.scrollbarXRail, 'borderLeftWidth')) + _.toInt(dom.css(i.scrollbarXRail, 'borderRightWidth'));
-  // Set rail to display:block to calculate margins
-  dom.css(i.scrollbarXRail, 'display', 'block');
-  i.railXMarginWidth = _.toInt(dom.css(i.scrollbarXRail, 'marginLeft')) + _.toInt(dom.css(i.scrollbarXRail, 'marginRight'));
-  dom.css(i.scrollbarXRail, 'display', '');
-  i.railXWidth = null;
-  i.railXRatio = null;
-
-  i.scrollbarYRail = dom.appendTo(dom.e('div', 'ps-scrollbar-y-rail'), element);
-  i.scrollbarY = dom.appendTo(dom.e('div', 'ps-scrollbar-y'), i.scrollbarYRail);
-  i.scrollbarY.setAttribute('tabindex', 0);
-  i.event.bind(i.scrollbarY, 'focus', focus);
-  i.event.bind(i.scrollbarY, 'blur', blur);
-  i.scrollbarYActive = null;
-  i.scrollbarYHeight = null;
-  i.scrollbarYTop = null;
-  i.scrollbarYRight = _.toInt(dom.css(i.scrollbarYRail, 'right'));
-  i.isScrollbarYUsingRight = i.scrollbarYRight === i.scrollbarYRight; // !isNaN
-  i.scrollbarYLeft = i.isScrollbarYUsingRight ? null : _.toInt(dom.css(i.scrollbarYRail, 'left'));
-  i.scrollbarYOuterWidth = i.isRtl ? _.outerWidth(i.scrollbarY) : null;
-  i.railBorderYWidth = _.toInt(dom.css(i.scrollbarYRail, 'borderTopWidth')) + _.toInt(dom.css(i.scrollbarYRail, 'borderBottomWidth'));
-  dom.css(i.scrollbarYRail, 'display', 'block');
-  i.railYMarginHeight = _.toInt(dom.css(i.scrollbarYRail, 'marginTop')) + _.toInt(dom.css(i.scrollbarYRail, 'marginBottom'));
-  dom.css(i.scrollbarYRail, 'display', '');
-  i.railYHeight = null;
-  i.railYRatio = null;
-}
-
-function getId(element) {
-  return element.getAttribute('data-ps-id');
-}
-
-function setId(element, id) {
-  element.setAttribute('data-ps-id', id);
-}
-
-function removeId(element) {
-  element.removeAttribute('data-ps-id');
-}
-
-exports.add = function (element) {
-  var newId = guid();
-  setId(element, newId);
-  instances[newId] = new Instance(element);
-  return instances[newId];
-};
-
-exports.remove = function (element) {
-  delete instances[getId(element)];
-  removeId(element);
-};
-
-exports.get = function (element) {
-  return instances[getId(element)];
-};
-
-},{"../lib/class":2,"../lib/dom":3,"../lib/event-manager":4,"../lib/guid":5,"../lib/helper":6,"./default-setting":8}],19:[function(require,module,exports){
-'use strict';
-
-var _ = require('../lib/helper');
-var cls = require('../lib/class');
-var dom = require('../lib/dom');
-var instances = require('./instances');
-var updateScroll = require('./update-scroll');
-
-function getThumbSize(i, thumbSize) {
-  if (i.settings.minScrollbarLength) {
-    thumbSize = Math.max(thumbSize, i.settings.minScrollbarLength);
-  }
-  if (i.settings.maxScrollbarLength) {
-    thumbSize = Math.min(thumbSize, i.settings.maxScrollbarLength);
-  }
-  return thumbSize;
-}
-
-function updateCss(element, i) {
-  var xRailOffset = {width: i.railXWidth};
-  if (i.isRtl) {
-    xRailOffset.left = i.negativeScrollAdjustment + element.scrollLeft + i.containerWidth - i.contentWidth;
-  } else {
-    xRailOffset.left = element.scrollLeft;
-  }
-  if (i.isScrollbarXUsingBottom) {
-    xRailOffset.bottom = i.scrollbarXBottom - element.scrollTop;
-  } else {
-    xRailOffset.top = i.scrollbarXTop + element.scrollTop;
-  }
-  dom.css(i.scrollbarXRail, xRailOffset);
-
-  var yRailOffset = {top: element.scrollTop, height: i.railYHeight};
-  if (i.isScrollbarYUsingRight) {
-    if (i.isRtl) {
-      yRailOffset.right = i.contentWidth - (i.negativeScrollAdjustment + element.scrollLeft) - i.scrollbarYRight - i.scrollbarYOuterWidth;
-    } else {
-      yRailOffset.right = i.scrollbarYRight - element.scrollLeft;
-    }
-  } else {
-    if (i.isRtl) {
-      yRailOffset.left = i.negativeScrollAdjustment + element.scrollLeft + i.containerWidth * 2 - i.contentWidth - i.scrollbarYLeft - i.scrollbarYOuterWidth;
-    } else {
-      yRailOffset.left = i.scrollbarYLeft + element.scrollLeft;
-    }
-  }
-  dom.css(i.scrollbarYRail, yRailOffset);
-
-  dom.css(i.scrollbarX, {left: i.scrollbarXLeft, width: i.scrollbarXWidth - i.railBorderXWidth});
-  dom.css(i.scrollbarY, {top: i.scrollbarYTop, height: i.scrollbarYHeight - i.railBorderYWidth});
-}
-
-module.exports = function (element) {
-  var i = instances.get(element);
-
-  i.containerWidth = element.clientWidth;
-  i.containerHeight = element.clientHeight;
-  i.contentWidth = element.scrollWidth;
-  i.contentHeight = element.scrollHeight;
-
-  var existingRails;
-  if (!element.contains(i.scrollbarXRail)) {
-    existingRails = dom.queryChildren(element, '.ps-scrollbar-x-rail');
-    if (existingRails.length > 0) {
-      existingRails.forEach(function (rail) {
-        dom.remove(rail);
-      });
-    }
-    dom.appendTo(i.scrollbarXRail, element);
-  }
-  if (!element.contains(i.scrollbarYRail)) {
-    existingRails = dom.queryChildren(element, '.ps-scrollbar-y-rail');
-    if (existingRails.length > 0) {
-      existingRails.forEach(function (rail) {
-        dom.remove(rail);
-      });
-    }
-    dom.appendTo(i.scrollbarYRail, element);
-  }
-
-  if (!i.settings.suppressScrollX && i.containerWidth + i.settings.scrollXMarginOffset < i.contentWidth) {
-    i.scrollbarXActive = true;
-    i.railXWidth = i.containerWidth - i.railXMarginWidth;
-    i.railXRatio = i.containerWidth / i.railXWidth;
-    i.scrollbarXWidth = getThumbSize(i, _.toInt(i.railXWidth * i.containerWidth / i.contentWidth));
-    i.scrollbarXLeft = _.toInt((i.negativeScrollAdjustment + element.scrollLeft) * (i.railXWidth - i.scrollbarXWidth) / (i.contentWidth - i.containerWidth));
-  } else {
-    i.scrollbarXActive = false;
-  }
-
-  if (!i.settings.suppressScrollY && i.containerHeight + i.settings.scrollYMarginOffset < i.contentHeight) {
-    i.scrollbarYActive = true;
-    i.railYHeight = i.containerHeight - i.railYMarginHeight;
-    i.railYRatio = i.containerHeight / i.railYHeight;
-    i.scrollbarYHeight = getThumbSize(i, _.toInt(i.railYHeight * i.containerHeight / i.contentHeight));
-    i.scrollbarYTop = _.toInt(element.scrollTop * (i.railYHeight - i.scrollbarYHeight) / (i.contentHeight - i.containerHeight));
-  } else {
-    i.scrollbarYActive = false;
-  }
-
-  if (i.scrollbarXLeft >= i.railXWidth - i.scrollbarXWidth) {
-    i.scrollbarXLeft = i.railXWidth - i.scrollbarXWidth;
-  }
-  if (i.scrollbarYTop >= i.railYHeight - i.scrollbarYHeight) {
-    i.scrollbarYTop = i.railYHeight - i.scrollbarYHeight;
-  }
-
-  updateCss(element, i);
-
-  if (i.scrollbarXActive) {
-    cls.add(element, 'ps-active-x');
-  } else {
-    cls.remove(element, 'ps-active-x');
-    i.scrollbarXWidth = 0;
-    i.scrollbarXLeft = 0;
-    updateScroll(element, 'left', 0);
-  }
-  if (i.scrollbarYActive) {
-    cls.add(element, 'ps-active-y');
-  } else {
-    cls.remove(element, 'ps-active-y');
-    i.scrollbarYHeight = 0;
-    i.scrollbarYTop = 0;
-    updateScroll(element, 'top', 0);
-  }
-};
-
-},{"../lib/class":2,"../lib/dom":3,"../lib/helper":6,"./instances":18,"./update-scroll":20}],20:[function(require,module,exports){
-'use strict';
-
-var instances = require('./instances');
-
-var lastTop;
-var lastLeft;
-
-var createDOMEvent = function (name) {
-  var event = document.createEvent("Event");
-  event.initEvent(name, true, true);
-  return event;
-};
-
-module.exports = function (element, axis, value) {
-  if (typeof element === 'undefined') {
-    throw 'You must provide an element to the update-scroll function';
-  }
-
-  if (typeof axis === 'undefined') {
-    throw 'You must provide an axis to the update-scroll function';
-  }
-
-  if (typeof value === 'undefined') {
-    throw 'You must provide a value to the update-scroll function';
-  }
-
-  if (axis === 'top' && value <= 0) {
-    element.scrollTop = value = 0; // don't allow negative scroll
-    element.dispatchEvent(createDOMEvent('ps-y-reach-start'));
-  }
-
-  if (axis === 'left' && value <= 0) {
-    element.scrollLeft = value = 0; // don't allow negative scroll
-    element.dispatchEvent(createDOMEvent('ps-x-reach-start'));
-  }
-
-  var i = instances.get(element);
-
-  if (axis === 'top' && value >= i.contentHeight - i.containerHeight) {
-    // don't allow scroll past container
-    value = i.contentHeight - i.containerHeight;
-    if (value - element.scrollTop <= 1) {
-      // mitigates rounding errors on non-subpixel scroll values
-      value = element.scrollTop;
-    } else {
-      element.scrollTop = value;
-    }
-    element.dispatchEvent(createDOMEvent('ps-y-reach-end'));
-  }
-
-  if (axis === 'left' && value >= i.contentWidth - i.containerWidth) {
-    // don't allow scroll past container
-    value = i.contentWidth - i.containerWidth;
-    if (value - element.scrollLeft <= 1) {
-      // mitigates rounding errors on non-subpixel scroll values
-      value = element.scrollLeft;
-    } else {
-      element.scrollLeft = value;
-    }
-    element.dispatchEvent(createDOMEvent('ps-x-reach-end'));
-  }
-
-  if (!lastTop) {
-    lastTop = element.scrollTop;
-  }
-
-  if (!lastLeft) {
-    lastLeft = element.scrollLeft;
-  }
-
-  if (axis === 'top' && value < lastTop) {
-    element.dispatchEvent(createDOMEvent('ps-scroll-up'));
-  }
-
-  if (axis === 'top' && value > lastTop) {
-    element.dispatchEvent(createDOMEvent('ps-scroll-down'));
-  }
-
-  if (axis === 'left' && value < lastLeft) {
-    element.dispatchEvent(createDOMEvent('ps-scroll-left'));
-  }
-
-  if (axis === 'left' && value > lastLeft) {
-    element.dispatchEvent(createDOMEvent('ps-scroll-right'));
-  }
-
-  if (axis === 'top') {
-    element.scrollTop = lastTop = value;
-    element.dispatchEvent(createDOMEvent('ps-scroll-y'));
-  }
-
-  if (axis === 'left') {
-    element.scrollLeft = lastLeft = value;
-    element.dispatchEvent(createDOMEvent('ps-scroll-x'));
-  }
-
-};
-
-},{"./instances":18}],21:[function(require,module,exports){
-'use strict';
-
-var _ = require('../lib/helper');
-var dom = require('../lib/dom');
-var instances = require('./instances');
-var updateGeometry = require('./update-geometry');
-var updateScroll = require('./update-scroll');
-
-module.exports = function (element) {
-  var i = instances.get(element);
-
-  if (!i) {
-    return;
-  }
-
-  // Recalcuate negative scrollLeft adjustment
-  i.negativeScrollAdjustment = i.isNegativeScroll ? element.scrollWidth - element.clientWidth : 0;
-
-  // Recalculate rail margins
-  dom.css(i.scrollbarXRail, 'display', 'block');
-  dom.css(i.scrollbarYRail, 'display', 'block');
-  i.railXMarginWidth = _.toInt(dom.css(i.scrollbarXRail, 'marginLeft')) + _.toInt(dom.css(i.scrollbarXRail, 'marginRight'));
-  i.railYMarginHeight = _.toInt(dom.css(i.scrollbarYRail, 'marginTop')) + _.toInt(dom.css(i.scrollbarYRail, 'marginBottom'));
-
-  // Hide scrollbars not to affect scrollWidth and scrollHeight
-  dom.css(i.scrollbarXRail, 'display', 'none');
-  dom.css(i.scrollbarYRail, 'display', 'none');
-
-  updateGeometry(element);
-
-  // Update top/left scroll to trigger events
-  updateScroll(element, 'top', element.scrollTop);
-  updateScroll(element, 'left', element.scrollLeft);
-
-  dom.css(i.scrollbarXRail, 'display', '');
-  dom.css(i.scrollbarYRail, 'display', '');
-};
-
-},{"../lib/dom":3,"../lib/helper":6,"./instances":18,"./update-geometry":19,"./update-scroll":20}]},{},[1]);
-
-/**
- * Provides utility functions for date operations.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Date/Util
- */
-define('WoltLabSuite/Core/Date/Util',['Language'], function(Language) {
-       "use strict";
-       
-       /**
-        * @exports     WoltLabSuite/Core/Date/Util
-        */
-       var DateUtil = {
-               /**
-                * Returns the formatted date.
-                * 
-                * @param       {Date}          date            date object
-                * @returns     {string}        formatted date
-                */
-               formatDate: function(date) {
-                       return this.format(date, Language.get('wcf.date.dateFormat'));
-               },
-               
-               /**
-                * Returns the formatted time.
-                * 
-                * @param       {Date}          date            date object
-                * @returns     {string}        formatted time
-                */
-               formatTime: function(date) {
-                       return this.format(date, Language.get('wcf.date.timeFormat'));
-               },
-               
-               /**
-                * Returns the formatted date time.
-                * 
-                * @param       {Date}          date            date object
-                * @returns     {string}        formatted date time
-                */
-               formatDateTime: function(date) {
-                       return this.format(date, Language.get('wcf.date.dateTimeFormat').replace(/%date%/, Language.get('wcf.date.dateFormat')).replace(/%time%/, Language.get('wcf.date.timeFormat')));
-               },
-               
-               /**
-                * Formats a date using PHP's `date()` modifiers.
-                * 
-                * @param       {Date}          date            date object
-                * @param       {string}        format          output format
-                * @returns     {string}        formatted date
-                */
-               format: function(date, format) {
-                       var char;
-                       var out = '';
-                       
-                       // ISO 8601 date, best recognition by PHP's strtotime()
-                       if (format === 'c') {
-                               format = 'Y-m-dTH:i:sP';
-                       }
-                       
-                       for (var i = 0, length = format.length; i < length; i++) {
-                               switch (format[i]) {
-                                       // seconds
-                                       case 's':
-                                               // `00` through `59`
-                                               char = ('0' + date.getSeconds().toString()).slice(-2);
-                                               break;
-                                       
-                                       // minutes
-                                       case 'i':
-                                               // `00` through `59`
-                                               char = date.getMinutes();
-                                               if (char < 10) char = "0" + char;
-                                               break;
-                                       
-                                       // hours
-                                       case 'a':
-                                               // `am` or `pm`
-                                               char = (date.getHours() > 11) ? 'pm' : 'am';
-                                               break;
-                                       case 'g':
-                                               // `1` through `12`
-                                               char = date.getHours();
-                                               if (char === 0) char = 12;
-                                               else if (char > 12) char -= 12;
-                                               break;
-                                       case 'h':
-                                               // `01` through `12`
-                                               char = date.getHours();
-                                               if (char === 0) char = 12;
-                                               else if (char > 12) char -= 12;
-                                               
-                                               char = ('0' + char.toString()).slice(-2);
-                                               break;
-                                       case 'A':
-                                               // `AM` or `PM`
-                                               char = (date.getHours() > 11) ? 'PM' : 'AM';
-                                               break;
-                                       case 'G':
-                                               // `0` through `23`
-                                               char = date.getHours();
-                                               break;
-                                       case 'H':
-                                               // `00` through `23`
-                                               char = date.getHours();
-                                               char = ('0' + char.toString()).slice(-2);
-                                               break;
-                                       
-                                       // day
-                                       case 'd':
-                                               // `01` through `31`
-                                               char = date.getDate();
-                                               char = ('0' + char.toString()).slice(-2);
-                                               break;
-                                       case 'j':
-                                               // `1` through `31`
-                                               char = date.getDate();
-                                               break;
-                                       case 'l':
-                                               // `Monday` through `Sunday` (localized)
-                                               char = Language.get('__days')[date.getDay()];
-                                               break;
-                                       case 'D':
-                                               // `Mon` through `Sun` (localized)
-                                               char = Language.get('__daysShort')[date.getDay()];
-                                               break;
-                                       case 'S':
-                                               // ignore english ordinal suffix
-                                               char = '';
-                                               break;
-                                       
-                                       // month
-                                       case 'm':
-                                               // `01` through `12`
-                                               char = date.getMonth() + 1;
-                                               char = ('0' + char.toString()).slice(-2);
-                                               break;
-                                       case 'n':
-                                               // `1` through `12`
-                                               char = date.getMonth() + 1;
-                                               break;
-                                       case 'F':
-                                               // `January` through `December` (localized)
-                                               char = Language.get('__months')[date.getMonth()];
-                                               break;
-                                       case 'M':
-                                               // `Jan` through `Dec` (localized)
-                                               char = Language.get('__monthsShort')[date.getMonth()];
-                                               break;
-                                       
-                                       // year
-                                       case 'y':
-                                               // `00` through `99`
-                                               char = date.getYear().toString().replace(/^\d{2}/, '');
-                                               break;
-                                       case 'Y':
-                                               // Examples: `1988` or `2015`
-                                               char = date.getFullYear();
-                                               break;
-                                       
-                                       // timezone
-                                       case 'P':
-                                               var offset = date.getTimezoneOffset();
-                                               char = (offset > 0) ? '-' : '+';
-                                               
-                                               offset = Math.abs(offset);
-                                               
-                                               char += ('0' + (~~(offset / 60)).toString()).slice(-2);
-                                               char += ':';
-                                               char += ('0' + (offset % 60).toString()).slice(-2);
-                                               
-                                               break;
-                                               
-                                       // specials
-                                       case 'r':
-                                               char = date.toString();
-                                               break;
-                                       case 'U':
-                                               char = Math.round(date.getTime() / 1000);
-                                               break;
-                                               
-                                       // escape sequence
-                                       case '\\':
-                                               char = '';
-                                               if (i + 1 < length) {
-                                                       char = format[++i];
-                                               }
-                                               break;
-                                       
-                                       default:
-                                               char = format[i];
-                                               break;
-                               }
-                               
-                               out += char;
-                       }
-                       
-                       return out;
-               },
-               
-               /**
-                * Returns UTC timestamp, if date is not given, current time will be used.
-                * 
-                * @param       {Date}          date    target date
-                * @return      {int}           UTC timestamp in seconds
-                */
-               gmdate: function(date) {
-                       if (!(date instanceof Date)) {
-                               date = new Date();
-                       }
-                       
-                       return Math.round(Date.UTC(
-                               date.getUTCFullYear(),
-                               date.getUTCMonth(),
-                               date.getUTCDay(),
-                               date.getUTCHours(),
-                               date.getUTCMinutes(),
-                               date.getUTCSeconds()
-                       ) / 1000);
-               },
-               
-               /**
-                * Returns a `time` element based on the given date just like a `time`
-                * element created by `wcf\system\template\plugin\TimeModifierTemplatePlugin`.
-                * 
-                * Note: The actual content of the element is empty and is expected
-                * to be automatically updated by `WoltLabSuite/Core/Date/Time/Relative`
-                * (for dates not in the future) after the DOM change listener has been triggered.
-                * 
-                * @param       {Date}          date    displayed date
-                * @return      {HTMLElement}   `time` element
-                */
-               getTimeElement: function(date) {
-                       var time = elCreate('time');
-                       time.className = 'datetime';
-                       
-                       var formattedDate = this.formatDate(date);
-                       var formattedTime = this.formatTime(date);
-                       
-                       elAttr(time, 'datetime', this.format(date, 'c'));
-                       elData(time, 'timestamp', (date.getTime() - date.getMilliseconds()) / 1000);
-                       elData(time, 'date', formattedDate);
-                       elData(time, 'time', formattedTime);
-                       elData(time, 'offset', date.getTimezoneOffset() * 60); // PHP returns minutes, JavaScript returns seconds
-                       
-                       if (date.getTime() > Date.now()) {
-                               elData(time, 'is-future-date', 'true');
-                               
-                               time.textContent = Language.get('wcf.date.dateTimeFormat').replace('%time%', formattedTime).replace('%date%', formattedDate);
-                       }
-                       
-                       return time;
-               },
-               
-               /**
-                * Returns a Date object with precise offset (including timezone and local timezone).
-                * 
-                * @param       {int}           timestamp       timestamp in milliseconds
-                * @param       {int}           offset          timezone offset in milliseconds
-                * @return      {Date}          localized date
-                */
-               getTimezoneDate: function(timestamp, offset) {
-                       var date = new Date(timestamp);
-                       var localOffset = date.getTimezoneOffset() * 60000;
-                       
-                       return new Date((timestamp + localOffset + offset));
-               }
-       };
-       
-       return DateUtil;
-});
-
-/**
- * Provides an object oriented API on top of `setInterval`.
- * 
- * @author     Tim Duesterhus
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Timer/Repeating
- */
-define('WoltLabSuite/Core/Timer/Repeating',[], function() {
-       "use strict";
-       
-       /**
-        * Creates a new timer that executes the given `callback` every `delta` milliseconds.
-        * It will be created in started mode. Call `stop()` if necessary.
-        * The `callback` will be passed the owning instance of `Repeating`.
-        * 
-        * @constructor
-        * @param       {function(Repeating)}   callback
-        * @param       {int}                   delta
-        */
-       function Repeating(callback, delta) {
-               if (typeof callback !== 'function') {
-                       throw new TypeError("Expected a valid callback as first argument.");
-               }
-               if (delta < 0 || delta > 86400 * 1000) {
-                       throw new RangeError("Invalid delta " + delta + ". Delta must be in the interval [0, 86400000].");
-               }
-               
-               // curry callback with `this` as the first parameter
-               this._callback = callback.bind(undefined, this);
-               
-               this._delta = delta;
-               this._timer = undefined;
-               
-               this.restart();
-       }
-       Repeating.prototype = {
-               /**
-                * Stops the timer and restarts it. The next call will occur in `delta` milliseconds.
-                */
-               restart: function() {
-                       this.stop();
-                       
-                       this._timer = setInterval(this._callback, this._delta);
-               },
-               
-               /**
-                * Stops the timer. It will no longer be called until you call `restart`.
-                */
-               stop: function() {
-                       if (this._timer !== undefined) {
-                               clearInterval(this._timer);
-                               this._timer = undefined;
-                       }
-               },
-               
-               /**
-                * Changes the `delta` of the timer and `restart`s it.
-                * 
-                * @param       {int}   delta   New delta of the timer.
-                */
-               setDelta: function(delta) {
-                       this._delta = delta;
-                       
-                       this.restart();
-               }
-       };
-       
-       return Repeating;
-});
-
-/**
- * Transforms <time> elements to display the elapsed time relative to the current time.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Date/Time/Relative
- */
-define('WoltLabSuite/Core/Date/Time/Relative',['Dom/ChangeListener', 'Language', 'WoltLabSuite/Core/Date/Util', 'WoltLabSuite/Core/Timer/Repeating'], function(DomChangeListener, Language, DateUtil, Repeating) {
-       "use strict";
-       
-       var _elements = elByTag('time');
-       var _isActive = true;
-       var _isPending = false;
-       var _offset = null;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Date/Time/Relative
-        */
-       return {
-               /**
-                * Transforms <time> elements on init and binds event listeners.
-                */
-               setup: function() {
-                       new Repeating(this._refresh.bind(this), 60000);
-                       
-                       DomChangeListener.add('WoltLabSuite/Core/Date/Time/Relative', this._refresh.bind(this));
-                       
-                       document.addEventListener('visibilitychange', this._onVisibilityChange.bind(this));
-               },
-               
-               _onVisibilityChange: function () {
-                       if (document.hidden) {
-                               _isActive = false;
-                               _isPending = false;
-                       }
-                       else {
-                               _isActive = true;
-                               
-                               // force immediate refresh
-                               if (_isPending) {
-                                       this._refresh();
-                                       _isPending = false;
-                               }
-                       }
-               },
-               
-               _refresh: function() {
-                       // activity is suspended while the tab is hidden, but force an
-                       // immediate refresh once the page is active again
-                       if (!_isActive) {
-                               if (!_isPending) _isPending = true;
-                               return;
-                       }
-                       
-                       var date = new Date();
-                       var timestamp = (date.getTime() - date.getMilliseconds()) / 1000;
-                       if (_offset === null) _offset = timestamp - window.TIME_NOW;
-                       
-                       for (var i = 0, length = _elements.length; i < length; i++) {
-                               var element = _elements[i];
-                               
-                               if (!element.classList.contains('datetime') || elData(element, 'is-future-date')) continue;
-                               
-                               var elTimestamp = ~~elData(element, 'timestamp') + _offset;
-                               var elDate = elData(element, 'date');
-                               var elTime = elData(element, 'time');
-                               var elOffset = elData(element, 'offset');
-                               
-                               if (!elAttr(element, 'title')) {
-                                       elAttr(element, 'title', Language.get('wcf.date.dateTimeFormat').replace(/%date%/, elDate).replace(/%time%/, elTime));
-                               }
-                               
-                               // timestamp is less than 60 seconds ago
-                               if (elTimestamp >= timestamp || timestamp < (elTimestamp + 60)) {
-                                       element.textContent = Language.get('wcf.date.relative.now');
-                               }
-                               // timestamp is less than 60 minutes ago (display 1 hour ago rather than 60 minutes ago)
-                               else if (timestamp < (elTimestamp + 3540)) {
-                                       var minutes = Math.max(Math.round((timestamp - elTimestamp) / 60), 1);
-                                       element.textContent = Language.get('wcf.date.relative.minutes', { minutes: minutes });
-                               }
-                               // timestamp is less than 24 hours ago
-                               else if (timestamp < (elTimestamp + 86400)) {
-                                       var hours = Math.round((timestamp - elTimestamp) / 3600);
-                                       element.textContent = Language.get('wcf.date.relative.hours', { hours: hours });
-                               }
-                               // timestamp is less than 6 days ago
-                               else if (timestamp < (elTimestamp + 518400)) {
-                                       var midnight = new Date(date.getFullYear(), date.getMonth(), date.getDate());
-                                       var days = Math.ceil((midnight / 1000 - elTimestamp) / 86400);
-                                       
-                                       // get day of week
-                                       var dateObj = DateUtil.getTimezoneDate((elTimestamp * 1000), elOffset * 1000);
-                                       var dow = dateObj.getDay();
-                                       var day = Language.get('__days')[dow];
-                                       
-                                       element.textContent = Language.get('wcf.date.relative.pastDays', { days: days, day: day, time: elTime });
-                               }
-                               // timestamp is between ~700 million years BC and last week
-                               else {
-                                       element.textContent = Language.get('wcf.date.shortDateTimeFormat').replace(/%date%/, elDate).replace(/%time%/, elTime);
-                               }
-                       }
-               }
-       };
-});
-
-/**
- * Provides a touch-friendly fullscreen menu.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Page/Menu/Abstract
- */
-define('WoltLabSuite/Core/Ui/Page/Menu/Abstract',['Core', 'Environment', 'EventHandler', 'Language', 'ObjectMap', 'Dom/Traverse', 'Dom/Util', 'Ui/Screen'], function(Core, Environment, EventHandler, Language, ObjectMap, DomTraverse, DomUtil, UiScreen) {
-       "use strict";
-       
-       var _pageContainer = elById('pageContainer');
-
-       /**
-        * Which edge of the menu is touched? Empty string
-        * if no menu is currently touched.
-        * 
-        * One 'left', 'right' or ''.
-        */
-       var _androidTouching = '';
-       
-       /**
-        * @param       {string}        eventIdentifier         event namespace
-        * @param       {string}        elementId               menu element id
-        * @param       {string}        buttonSelector          CSS selector for toggle button
-        * @constructor
-        */
-       function UiPageMenuAbstract(eventIdentifier, elementId, buttonSelector) { this.init(eventIdentifier, elementId, buttonSelector); }
-       UiPageMenuAbstract.prototype = {
-               /**
-                * Initializes a touch-friendly fullscreen menu.
-                * 
-                * @param       {string}        eventIdentifier         event namespace
-                * @param       {string}        elementId               menu element id
-                * @param       {string}        buttonSelector          CSS selector for toggle button
-                */
-               init: function(eventIdentifier, elementId, buttonSelector) {
-                       if (elData(document.body, 'template') === 'packageInstallationSetup') {
-                               // work-around for WCFSetup on mobile
-                               return;
-                       }
-                       
-                       this._activeList = [];
-                       this._depth = 0;
-                       this._enabled = true;
-                       this._eventIdentifier = eventIdentifier;
-                       this._items = new ObjectMap();
-                       this._menu = elById(elementId);
-                       this._removeActiveList = false;
-                       
-                       var callbackOpen = this.open.bind(this);
-                       this._button = elBySel(buttonSelector);
-                       this._button.addEventListener(WCF_CLICK_EVENT, callbackOpen);
-                       
-                       this._initItems();
-                       this._initHeader();
-                       
-                       EventHandler.add(this._eventIdentifier, 'open', callbackOpen);
-                       EventHandler.add(this._eventIdentifier, 'close', this.close.bind(this));
-                       EventHandler.add(this._eventIdentifier, 'updateButtonState', this._updateButtonState.bind(this));
-                       
-                       var itemList, itemLists = elByClass('menuOverlayItemList', this._menu);
-                       this._menu.addEventListener('animationend', (function() {
-                               if (!this._menu.classList.contains('open')) {
-                                       for (var i = 0, length = itemLists.length; i < length; i++) {
-                                               itemList = itemLists[i];
-                                               
-                                               // force the main list to be displayed
-                                               itemList.classList.remove('active');
-                                               itemList.classList.remove('hidden');
-                                       }
-                               }
-                       }).bind(this));
-                       
-                       this._menu.children[0].addEventListener('transitionend', (function() {
-                               this._menu.classList.add('allowScroll');
-                               
-                               if (this._removeActiveList) {
-                                       this._removeActiveList = false;
-                                       
-                                       var list = this._activeList.pop();
-                                       if (list) {
-                                               list.classList.remove('activeList');
-                                       }
-                               }
-                       }).bind(this));
-                       
-                       var backdrop = elCreate('div');
-                       backdrop.className = 'menuOverlayMobileBackdrop';
-                       backdrop.addEventListener(WCF_CLICK_EVENT, this.close.bind(this));
-                       
-                       DomUtil.insertAfter(backdrop, this._menu);
-                       
-                       this._updateButtonState();
-                       
-                       if (Environment.platform() === 'android') {
-                               this._initializeAndroid();
-                       }
-               },
-               
-               /**
-                * Opens the menu.
-                * 
-                * @param       {Event}         event   event object
-                * @return      {boolean}       true if menu has been opened
-                */
-               open: function(event) {
-                       if (!this._enabled) {
-                               return false;
-                       }
-                       
-                       if (event instanceof Event) {
-                               event.preventDefault();
-                       }
-                       
-                       this._menu.classList.add('open');
-                       this._menu.classList.add('allowScroll');
-                       this._menu.children[0].classList.add('activeList');
-                       
-                       UiScreen.scrollDisable();
-                       
-                       _pageContainer.classList.add('menuOverlay-' + this._menu.id);
-                       
-                       UiScreen.pageOverlayOpen();
-                       
-                       return true;
-               },
-               
-               /**
-                * Closes the menu.
-                * 
-                * @param       {(Event|boolean)}       event   event object or boolean true to force close the menu
-                * @return      {boolean}               true if menu was open
-                */
-               close: function(event) {
-                       if (event instanceof Event) {
-                               event.preventDefault();
-                       }
-                       
-                       if (this._menu.classList.contains('open')) {
-                               this._menu.classList.remove('open');
-                               
-                               UiScreen.scrollEnable();
-                               UiScreen.pageOverlayClose();
-                               
-                               _pageContainer.classList.remove('menuOverlay-' + this._menu.id);
-                               
-                               return true;
-                       }
-                       
-                       return false;
-               },
-               
-               /**
-                * Enables the touch menu.
-                */
-               enable: function() {
-                       this._enabled = true;
-               },
-               
-               /**
-                * Disables the touch menu.
-                */
-               disable: function() {
-                       this._enabled = false;
-                       
-                       this.close(true);
-               },
-               
-               /**
-                * Initializes the Android Touch Menu.
-                */
-               _initializeAndroid: function() {
-                       var appearsAt, backdrop, touchStart;
-                       /** @const */ var AT_EDGE = 20;
-                       /** @const */ var MOVED_HORIZONTALLY = 5;
-                       /** @const */ var MOVED_VERTICALLY = 20;
-                       
-                       // specify on which side of the page the menu appears
-                       switch (this._menu.id) {
-                               case 'pageUserMenuMobile':
-                                       appearsAt = 'right';
-                               break;
-                               case 'pageMainMenuMobile':
-                                       appearsAt = 'left';
-                               break;
-                               default:
-                                       return;
-                       }
-                       
-                       backdrop = this._menu.nextElementSibling;
-                       
-                       // horizontal position of the touch start
-                       touchStart = null;
-                       
-                       document.addEventListener('touchstart', (function(event) {
-                               var touches, isOpen, isLeftEdge, isRightEdge;
-                               touches = event.touches;
-                               
-                               isOpen = this._menu.classList.contains('open');
-                               
-                               // check whether we touch the edges of the menu
-                               if (appearsAt === 'left') {
-                                       isLeftEdge = !isOpen && (touches[0].clientX < AT_EDGE);
-                                       isRightEdge = isOpen && (Math.abs(this._menu.offsetWidth - touches[0].clientX) < AT_EDGE);
-                               }
-                               else if (appearsAt === 'right') {
-                                       isLeftEdge = isOpen && (Math.abs(document.body.clientWidth - this._menu.offsetWidth - touches[0].clientX) < AT_EDGE);
-                                       isRightEdge = !isOpen && ((document.body.clientWidth - touches[0].clientX) < AT_EDGE);
-                               }
-                               
-                               // abort if more than one touch
-                               if (touches.length > 1) {
-                                       if (_androidTouching) {
-                                               Core.triggerEvent(document, 'touchend');
-                                       }
-                                       return;
-                               }
-                               
-                               // break if a touch is in progress
-                               if (_androidTouching) return;
-                               // break if no edge has been touched
-                               if (!isLeftEdge && !isRightEdge) return;
-                               // break if a different menu is open
-                               if (UiScreen.pageOverlayIsActive()) {
-                                       var found = false;
-                                       for (var i = 0; i < _pageContainer.classList.length; i++) {
-                                               if (_pageContainer.classList[i] === 'menuOverlay-' + this._menu.id) {
-                                                       found = true;
-                                               }
-                                       }
-                                       if (!found) return;
-                               }
-                               // break if redactor is in use
-                               if (document.documentElement.classList.contains('redactorActive')) return;
-                               
-                               touchStart = {
-                                       x: touches[0].clientX,
-                                       y: touches[0].clientY
-                               };
-                               
-                               if (isLeftEdge) _androidTouching = 'left';
-                               if (isRightEdge) _androidTouching = 'right';
-                       }).bind(this));
-                       
-                       document.addEventListener('touchend', (function(event) {
-                               // break if we did not start a touch
-                               if (!_androidTouching || touchStart === null) return;
-                               
-                               // break if the menu did not even start opening
-                               if (!this._menu.classList.contains('open')) {
-                                       // reset
-                                       touchStart = null;
-                                       _androidTouching = '';
-                                       return;
-                               }
-                               
-                               // last known position of the finger
-                               var position;
-                               if (event) {
-                                       position = event.changedTouches[0].clientX;
-                               }
-                               else {
-                                       position = touchStart.x;
-                               }
-                               
-                               // clean up touch styles
-                               this._menu.classList.add('androidMenuTouchEnd');
-                               this._menu.style.removeProperty('transform');
-                               backdrop.style.removeProperty(appearsAt);
-                               this._menu.addEventListener('transitionend', (function() {
-                                       this._menu.classList.remove('androidMenuTouchEnd');
-                               }).bind(this), { once: true });
-                               
-                               // check whether the user moved the finger far enough
-                               if (appearsAt === 'left') {
-                                       if (_androidTouching === 'left' && position < (touchStart.x + 100)) this.close();
-                                       if (_androidTouching === 'right' && position < (touchStart.x - 100)) this.close();
-                               }
-                               else if (appearsAt === 'right') {
-                                       if (_androidTouching === 'left' && position > (touchStart.x + 100)) this.close();
-                                       if (_androidTouching === 'right' && position > (touchStart.x - 100)) this.close();
-                               }
-                               
-                               // reset
-                               touchStart = null;
-                               _androidTouching = '';
-                       }).bind(this));
-                       
-                       document.addEventListener('touchmove', (function(event) {
-                               // break if we did not start a touch
-                               if (!_androidTouching || touchStart === null) return;
-                               
-                               var touches = event.touches;
-                               
-                               // check whether the user started moving in the correct direction
-                               // this avoids false positives, in case the user just wanted to tap
-                               var movedFromEdge = false, movedVertically = false;
-                               if (_androidTouching === 'left') movedFromEdge = touches[0].clientX > (touchStart.x + MOVED_HORIZONTALLY);
-                               if (_androidTouching === 'right') movedFromEdge = touches[0].clientX < (touchStart.x - MOVED_HORIZONTALLY);
-                               movedVertically = Math.abs(touches[0].clientY - touchStart.y) > MOVED_VERTICALLY;
-                               
-                               var isOpen = this._menu.classList.contains('open');
-                               
-                               if (!isOpen && movedFromEdge && !movedVertically) {
-                                       // the menu is not yet open, but the user moved into the right direction
-                                       this.open();
-                                       isOpen = true;
-                               }
-                               
-                               if (isOpen) {
-                                       // update CSS to the new finger position
-                                       var position = touches[0].clientX;
-                                       if (appearsAt === 'right') position = document.body.clientWidth - position;
-                                       if (position > this._menu.offsetWidth) position = this._menu.offsetWidth;
-                                       if (position < 0) position = 0;
-                                       this._menu.style.setProperty('transform', 'translateX(' + (appearsAt === 'left' ? 1 : -1) * (position - this._menu.offsetWidth) + 'px)');
-                                       backdrop.style.setProperty(appearsAt, Math.min(this._menu.offsetWidth, position) + 'px');
-                               }
-                       }).bind(this));
-               },
-               
-               /**
-                * Initializes all menu items.
-                * 
-                * @protected
-                */
-               _initItems: function() {
-                       elBySelAll('.menuOverlayItemLink', this._menu, this._initItem.bind(this));
-               },
-               
-               /**
-                * Initializes a single menu item.
-                * 
-                * @param       {Element}       item    menu item
-                * @protected
-                */
-               _initItem: function(item) {
-                       // check if it should contain a 'more' link w/ an external callback
-                       var parent = item.parentNode;
-                       var more = elData(parent, 'more');
-                       if (more) {
-                               item.addEventListener(WCF_CLICK_EVENT, (function(event) {
-                                       event.preventDefault();
-                                       event.stopPropagation();
-                                       
-                                       EventHandler.fire(this._eventIdentifier, 'more', {
-                                               handler: this,
-                                               identifier: more,
-                                               item: item,
-                                               parent: parent
-                                       });
-                               }).bind(this));
-                               
-                               return;
-                       }
-                       
-                       var itemList = item.nextElementSibling, wrapper;
-                       if (itemList === null) {
-                               return;
-                       }
-                       
-                       // handle static items with an icon-type button next to it (acp menu)
-                       if (itemList.nodeName !== 'OL' && itemList.classList.contains('menuOverlayItemLinkIcon')) {
-                               // add wrapper
-                               wrapper = elCreate('span');
-                               wrapper.className = 'menuOverlayItemWrapper';
-                               parent.insertBefore(wrapper, item);
-                               wrapper.appendChild(item);
-                               
-                               while (wrapper.nextElementSibling) {
-                                       wrapper.appendChild(wrapper.nextElementSibling);
-                               }
-                               
-                               return;
-                       }
-                       
-                       var isLink = (elAttr(item, 'href') !== '#');
-                       var parentItemList = parent.parentNode;
-                       var itemTitle = elData(itemList, 'title');
-                       
-                       this._items.set(item, {
-                               itemList: itemList,
-                               parentItemList: parentItemList
-                       });
-                       
-                       if (itemTitle === '') {
-                               itemTitle = DomTraverse.childByClass(item, 'menuOverlayItemTitle').textContent;
-                               elData(itemList, 'title', itemTitle);
-                       }
-                       
-                       var callbackLink = this._showItemList.bind(this, item);
-                       if (isLink) {
-                               wrapper = elCreate('span');
-                               wrapper.className = 'menuOverlayItemWrapper';
-                               parent.insertBefore(wrapper, item);
-                               wrapper.appendChild(item);
-                               
-                               var moreLink = elCreate('a');
-                               elAttr(moreLink, 'href', '#');
-                               moreLink.className = 'menuOverlayItemLinkIcon' + (item.classList.contains('active') ? ' active' : '');
-                               moreLink.innerHTML = '<span class="icon icon24 fa-angle-right"></span>';
-                               moreLink.addEventListener(WCF_CLICK_EVENT, callbackLink);
-                               wrapper.appendChild(moreLink);
-                       }
-                       else {
-                               item.classList.add('menuOverlayItemLinkMore');
-                               item.addEventListener(WCF_CLICK_EVENT, callbackLink);
-                       }
-                       
-                       var backLinkItem = elCreate('li');
-                       backLinkItem.className = 'menuOverlayHeader';
-                       
-                       wrapper = elCreate('span');
-                       wrapper.className = 'menuOverlayItemWrapper';
-                       
-                       var backLink = elCreate('a');
-                       elAttr(backLink, 'href', '#');
-                       backLink.className = 'menuOverlayItemLink menuOverlayBackLink';
-                       backLink.textContent = elData(parentItemList, 'title');
-                       backLink.addEventListener(WCF_CLICK_EVENT, this._hideItemList.bind(this, item));
-                       
-                       var closeLink = elCreate('a');
-                       elAttr(closeLink, 'href', '#');
-                       closeLink.className = 'menuOverlayItemLinkIcon';
-                       closeLink.innerHTML = '<span class="icon icon24 fa-times"></span>';
-                       closeLink.addEventListener(WCF_CLICK_EVENT, this.close.bind(this));
-                       
-                       wrapper.appendChild(backLink);
-                       wrapper.appendChild(closeLink);
-                       backLinkItem.appendChild(wrapper);
-                       
-                       itemList.insertBefore(backLinkItem, itemList.firstElementChild);
-                       
-                       if (!backLinkItem.nextElementSibling.classList.contains('menuOverlayTitle')) {
-                               var titleItem = elCreate('li');
-                               titleItem.className = 'menuOverlayTitle';
-                               var title = elCreate('span');
-                               title.textContent = itemTitle;
-                               titleItem.appendChild(title);
-                               
-                               itemList.insertBefore(titleItem, backLinkItem.nextElementSibling);
-                       }
-               },
-               
-               /**
-                * Renders the menu item list header.
-                * 
-                * @protected
-                */
-               _initHeader: function() {
-                       var listItem = elCreate('li');
-                       listItem.className = 'menuOverlayHeader';
-                       
-                       var wrapper = elCreate('span');
-                       wrapper.className = 'menuOverlayItemWrapper';
-                       listItem.appendChild(wrapper);
-                       
-                       var logoWrapper = elCreate('span');
-                       logoWrapper.className = 'menuOverlayLogoWrapper';
-                       wrapper.appendChild(logoWrapper);
-                       
-                       var logo = elCreate('span');
-                       logo.className = 'menuOverlayLogo';
-                       logo.style.setProperty('background-image', 'url("' + elData(this._menu, 'page-logo') + '")', '');
-                       logoWrapper.appendChild(logo);
-                       
-                       var closeLink = elCreate('a');
-                       elAttr(closeLink, 'href', '#');
-                       closeLink.className = 'menuOverlayItemLinkIcon';
-                       closeLink.innerHTML = '<span class="icon icon24 fa-times"></span>';
-                       closeLink.addEventListener(WCF_CLICK_EVENT, this.close.bind(this));
-                       wrapper.appendChild(closeLink);
-                       
-                       var list = DomTraverse.childByClass(this._menu, 'menuOverlayItemList');
-                       list.insertBefore(listItem, list.firstElementChild);
-               },
-               
-               /**
-                * Hides an item list, return to the parent item list.
-                * 
-                * @param       {Element}       item    menu item
-                * @param       {Event}         event   event object
-                * @protected
-                */
-               _hideItemList: function(item, event) {
-                       if (event instanceof Event) {
-                               event.preventDefault();
-                       }
-                       
-                       this._menu.classList.remove('allowScroll');
-                       this._removeActiveList = true;
-                       
-                       var data = this._items.get(item);
-                       data.parentItemList.classList.remove('hidden');
-                       
-                       this._updateDepth(false);
-               },
-               
-               /**
-                * Shows the child item list.
-                * 
-                * @param       {Element}       item    menu item
-                * @param event
-                * @private
-                */
-               _showItemList: function(item, event) {
-                       if (event instanceof Event) {
-                               event.preventDefault();
-                       }
-                       
-                       var data = this._items.get(item);
-                       
-                       var load = elData(data.itemList, 'load');
-                       if (load) {
-                               if (!elDataBool(item, 'loaded')) {
-                                       var icon = event.currentTarget.firstElementChild;
-                                       if (icon.classList.contains('fa-angle-right')) {
-                                               icon.classList.remove('fa-angle-right');
-                                               icon.classList.add('fa-spinner');
-                                       }
-                                       
-                                       EventHandler.fire(this._eventIdentifier, 'load_' + load);
-                                       
-                                       return;
-                               }
-                       }
-                       
-                       this._menu.classList.remove('allowScroll');
-                       
-                       data.itemList.classList.add('activeList');
-                       data.parentItemList.classList.add('hidden');
-                       
-                       this._activeList.push(data.itemList);
-                       
-                       this._updateDepth(true);
-               },
-               
-               _updateDepth: function(increase) {
-                       this._depth += (increase) ? 1 : -1;
-                       
-                       var offset = this._depth * -100;
-                       if (Language.get('wcf.global.pageDirection') === 'rtl') {
-                               // reverse logic for RTL
-                               offset *= -1;
-                       }
-                       
-                       this._menu.children[0].style.setProperty('transform', 'translateX(' + offset + '%)', '');
-               },
-               
-               _updateButtonState: function() {
-                       var hasNewContent = false;
-                       elBySelAll('.badgeUpdate', this._menu, function (badge) {
-                               if (~~badge.textContent > 0) {
-                                       hasNewContent = true;
-                               }
-                       });
-                       
-                       this._button.classList[(hasNewContent ? 'add' : 'remove')]('pageMenuMobileButtonHasContent');
-               }
-       };
-       
-       return UiPageMenuAbstract;
-});
-
-/**
- * Provides the touch-friendly fullscreen main menu.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Page/Menu/Main
- */
-define('WoltLabSuite/Core/Ui/Page/Menu/Main',['Core', 'Language', 'Dom/Traverse', './Abstract'], function(Core, Language, DomTraverse, UiPageMenuAbstract) {
-       "use strict";
-       
-       var _optionsTitle = null, _hasItems = null, _list = null, _navigationList = null, _callbackClose = null;
-       
-       /**
-        * @constructor
-        */
-       function UiPageMenuMain() { this.init(); }
-       Core.inherit(UiPageMenuMain, UiPageMenuAbstract, {
-               /**
-                * Initializes the touch-friendly fullscreen main menu.
-                */
-               init: function() {
-                       UiPageMenuMain._super.prototype.init.call(
-                               this,
-                               'com.woltlab.wcf.MainMenuMobile',
-                               'pageMainMenuMobile',
-                               '#pageHeader .mainMenu'
-                       );
-                       
-                       _optionsTitle = elById('pageMainMenuMobilePageOptionsTitle');
-                       if (_optionsTitle !== null) {
-                               _list = DomTraverse.childByClass(_optionsTitle, 'menuOverlayItemList');
-                               _navigationList = elBySel('.jsPageNavigationIcons');
-                               
-                               _callbackClose = (function(event) {
-                                       this.close();
-                                       event.stopPropagation();
-                               }).bind(this);
-                       }
-                       
-                       elAttr(this._button, 'aria-label', Language.get('wcf.menu.page'));
-                       elAttr(this._button, 'role', 'button');
-               },
-               
-               open: function (event) {
-                       if (!UiPageMenuMain._super.prototype.open.call(this, event)) {
-                               return false;
-                       }
-                       
-                       if (_optionsTitle === null) {
-                               return true;
-                       }
-                       
-                       _hasItems = _navigationList && _navigationList.childElementCount > 0;
-                       
-                       if (_hasItems) {
-                               var item, link;
-                               while (_navigationList.childElementCount) {
-                                       item = _navigationList.children[0];
-                                       
-                                       item.classList.add('menuOverlayItem');
-                                       item.classList.add('menuOverlayItemOption');
-                                       item.addEventListener(WCF_CLICK_EVENT, _callbackClose);
-                                       
-                                       link = item.children[0];
-                                       link.classList.add('menuOverlayItemLink');
-                                       link.classList.add('box24');
-                                       
-                                       link.children[1].classList.remove('invisible');
-                                       link.children[1].classList.add('menuOverlayItemTitle');
-                                       
-                                       _optionsTitle.parentNode.insertBefore(item, _optionsTitle.nextSibling);
-                               }
-                               
-                               elShow(_optionsTitle);
-                       }
-                       else {
-                               elHide(_optionsTitle);
-                       }
-                       
-                       return true;
-               },
-               
-               close: function(event) {
-                       if (!UiPageMenuMain._super.prototype.close.call(this, event)) {
-                               return false;
-                       }
-                       
-                       if (_hasItems) {
-                               elHide(_optionsTitle);
-                               
-                               var item = _optionsTitle.nextElementSibling;
-                               var link;
-                               while (item && item.classList.contains('menuOverlayItemOption')) {
-                                       item.classList.remove('menuOverlayItem');
-                                       item.classList.remove('menuOverlayItemOption');
-                                       item.removeEventListener(WCF_CLICK_EVENT, _callbackClose);
-                                       
-                                       link = item.children[0];
-                                       link.classList.remove('menuOverlayItemLink');
-                                       link.classList.remove('box24');
-                                       
-                                       link.children[1].classList.add('invisible');
-                                       link.children[1].classList.remove('menuOverlayItemTitle');
-                                       
-                                       _navigationList.appendChild(item);
-                                       
-                                       item = item.nextElementSibling;
-                               }
-                       }
-                       
-                       return true;
-               }
-       });
-       
-       return UiPageMenuMain;
-});
-
-/**
- * Provides the touch-friendly fullscreen user menu.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Page/Menu/User
- */
-define('WoltLabSuite/Core/Ui/Page/Menu/User',['Core', 'EventHandler', 'Language', './Abstract'], function(Core, EventHandler, Language, UiPageMenuAbstract) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function UiPageMenuUser() { this.init(); }
-       Core.inherit(UiPageMenuUser, UiPageMenuAbstract, {
-               /**
-                * Initializes the touch-friendly fullscreen user menu.
-                */
-               init: function() {
-                       // check if user menu is actually empty
-                       var menu = elBySel('#pageUserMenuMobile > .menuOverlayItemList');
-                       if (menu.childElementCount === 1 && menu.children[0].classList.contains('menuOverlayTitle')) {
-                               elBySel('#pageHeader .userPanel').classList.add('hideUserPanel');
-                               return;
-                       }
-                       
-                       UiPageMenuUser._super.prototype.init.call(
-                               this,
-                               'com.woltlab.wcf.UserMenuMobile',
-                               'pageUserMenuMobile',
-                               '#pageHeader .userPanel'
-                       );
-                       
-                       EventHandler.add('com.woltlab.wcf.userMenu', 'updateBadge', (function (data) {
-                               elBySelAll('.menuOverlayItemBadge', this._menu, (function (item) {
-                                       if (elData(item, 'badge-identifier') === data.identifier) {
-                                               var badge = elBySel('.badge', item);
-                                               if (data.count) {
-                                                       if (badge === null) {
-                                                               badge = elCreate('span');
-                                                               badge.className = 'badge badgeUpdate';
-                                                               item.appendChild(badge);
-                                                       }
-                                                       
-                                                       badge.textContent = data.count;
-                                               }
-                                               else if (badge !== null) {
-                                                       elRemove(badge);
-                                               }
-                                               
-                                               this._updateButtonState();
-                                       }
-                               }).bind(this));
-                       }).bind(this));
-                       
-                       elAttr(this._button, 'aria-label', Language.get('wcf.menu.user'));
-                       elAttr(this._button, 'role', 'button');
-               },
-               
-               close: function (event) {
-                       var dropdown = WCF.Dropdown.Interactive.Handler.getOpenDropdown();
-                       if (dropdown) {
-                               event.preventDefault();
-                               event.stopPropagation();
-                               
-                               dropdown.close();
-                       }
-                       else {
-                               UiPageMenuUser._super.prototype.close.call(this, event);
-                       }
-               }
-       });
-       
-       return UiPageMenuUser;
-});
-
-/**
- * Simple interface to work with reusable dropdowns that are not bound to a specific item.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Dropdown/Reusable
- */
-define('WoltLabSuite/Core/Ui/Dropdown/Reusable',['Dictionary', 'Ui/SimpleDropdown'], function(Dictionary, UiSimpleDropdown) {
-       "use strict";
-       
-       var _dropdowns = new Dictionary();
-       var _ghostElementId = 0;
-       
-       /**
-        * Returns dropdown name by internal identifier.
-        *
-        * @param       {string}        identifier      internal identifier
-        * @returns     {string}        dropdown name
-        */
-       function _getDropdownName(identifier) {
-               if (!_dropdowns.has(identifier)) {
-                       throw new Error("Unknown dropdown identifier '" + identifier + "'");
-               }
-               
-               return _dropdowns.get(identifier);
-       }
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Dropdown/Reusable
-        */
-       return {
-               /**
-                * Initializes a new reusable dropdown.
-                * 
-                * @param       {string}        identifier      internal identifier
-                * @param       {Element}       menu            dropdown menu element
-                */
-               init: function(identifier, menu) {
-                       if (_dropdowns.has(identifier)) {
-                               return;
-                       }
-                       
-                       var ghostElement = elCreate('div');
-                       ghostElement.id = 'reusableDropdownGhost' + _ghostElementId++;
-                       
-                       UiSimpleDropdown.initFragment(ghostElement, menu);
-                       
-                       _dropdowns.set(identifier, ghostElement.id);
-               },
-               
-               /**
-                * Returns the dropdown menu element.
-                * 
-                * @param       {string}        identifier      internal identifier
-                * @returns     {Element}       dropdown menu element
-                */
-               getDropdownMenu: function(identifier) {
-                       return UiSimpleDropdown.getDropdownMenu(_getDropdownName(identifier));
-               },
-               
-               /**
-                * Registers a callback invoked upon open and close.
-                * 
-                * @param       {string}        identifier      internal identifier
-                * @param       {function}      callback        callback function
-                */
-               registerCallback: function(identifier, callback) {
-                       UiSimpleDropdown.registerCallback(_getDropdownName(identifier), callback);
-               },
-               
-               /**
-                * Toggles a dropdown.
-                * 
-                * @param       {string}        identifier              internal identifier
-                * @param       {Element}       referenceElement        reference element used for alignment
-                */
-               toggleDropdown: function(identifier, referenceElement) {
-                       UiSimpleDropdown.toggleDropdown(_getDropdownName(identifier), referenceElement);
-               }
-       };
-});
-
-/**
- * Modifies the interface to provide a better usability for mobile devices.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Mobile
- */
-define(
-       'WoltLabSuite/Core/Ui/Mobile',[        'Core', 'Environment', 'EventHandler', 'Language', 'List', 'Dom/ChangeListener', 'Dom/Traverse', 'Ui/Alignment', 'Ui/CloseOverlay', 'Ui/Screen', './Page/Menu/Main', './Page/Menu/User', 'WoltLabSuite/Core/Ui/Dropdown/Reusable'],
-       function(Core,    Environment,   EventHandler,   Language,   List,   DomChangeListener,    DomTraverse,    UiAlignment, UiCloseOverlay,    UiScreen,    UiPageMenuMain,     UiPageMenuUser, UiDropdownReusable)
-{
-       "use strict";
-       
-       var _buttonGroupNavigations = elByClass('buttonGroupNavigation');
-       var _callbackCloseDropdown = null;
-       var _dropdownMenu = null;
-       var _dropdownMenuMessage = null;
-       var _enabled = false;
-       var _knownMessages = new List();
-       var _main = null;
-       var _messages = elByClass('message');
-       var _options = {};
-       var _pageMenuMain = null;
-       var _pageMenuUser = null;
-       var _messageGroups = null;
-       var _sidebars = [];
-       var _sidebarXsEnabled = false;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Mobile
-        */
-       return {
-               /**
-                * Initializes the mobile UI.
-                * 
-                * @param       {Object=}       options         initialization options
-                */
-               setup: function(options) {
-                       _options = Core.extend({
-                               enableMobileMenu: true
-                       }, options);
-                       
-                       _main = elById('main');
-                       
-                       elBySelAll('.sidebar', undefined, function (sidebar) {
-                               _sidebars.push(sidebar);
-                       });
-                       
-                       if (Environment.touch()) {
-                               document.documentElement.classList.add('touch');
-                       }
-                       
-                       if (Environment.platform() !== 'desktop') {
-                               document.documentElement.classList.add('mobile');
-                       }
-                       
-                       var messageGroupList = elBySel('.messageGroupList');
-                       if (messageGroupList) _messageGroups = elByClass('messageGroup', messageGroupList);
-                       
-                       UiScreen.on('screen-md-down', {
-                               match: this.enable.bind(this),
-                               unmatch: this.disable.bind(this),
-                               setup: this._init.bind(this)
-                       });
-                       
-                       UiScreen.on('screen-sm-down', {
-                               match: this.enableShadow.bind(this),
-                               unmatch: this.disableShadow.bind(this),
-                               setup: this.enableShadow.bind(this)
-                       });
-                       
-                       UiScreen.on('screen-xs', {
-                               match: this._enableSidebarXS.bind(this),
-                               unmatch: this._disableSidebarXS.bind(this),
-                               setup: this._setupSidebarXS.bind(this)
-                       });
-               },
-               
-               /**
-                * Enables the mobile UI.
-                */
-               enable: function() {
-                       _enabled = true;
-                       
-                       if (_options.enableMobileMenu) {
-                               _pageMenuMain.enable();
-                               _pageMenuUser.enable();
-                       }
-               },
-               
-               /**
-                * Enables shadow links for larger click areas on messages. 
-                */
-               enableShadow: function () {
-                       if (_messageGroups) this.rebuildShadow(_messageGroups, '.messageGroupLink');
-               },
-               
-               /**
-                * Disables the mobile UI.
-                */
-               disable: function() {
-                       _enabled = false;
-                       
-                       if (_options.enableMobileMenu) {
-                               _pageMenuMain.disable();
-                               _pageMenuUser.disable();
-                       }
-               },
-               
-               /**
-                * Disables shadow links.
-                */
-               disableShadow: function () {
-                       if (_messageGroups) this.removeShadow(_messageGroups);
-                       
-                       if (_dropdownMenu) _callbackCloseDropdown();
-               },
-               
-               _init: function() {
-                       _enabled = true;
-                       
-                       this._initSearchBar();
-                       this._initButtonGroupNavigation();
-                       this._initMessages();
-                       this._initMobileMenu();
-                       
-                       UiCloseOverlay.add('WoltLabSuite/Core/Ui/Mobile', this._closeAllMenus.bind(this));
-                       DomChangeListener.add('WoltLabSuite/Core/Ui/Mobile', (function() {
-                               this._initButtonGroupNavigation();
-                               this._initMessages();
-                       }).bind(this));
-               },
-               
-               _initSearchBar: function() {
-                       var _searchBar = elById('pageHeaderSearch');
-                       var _searchInput = elById('pageHeaderSearchInput');
-                       
-                       var scrollTop = null;
-                       
-                       EventHandler.add('com.woltlab.wcf.MainMenuMobile', 'more', function(data) {
-                               if (data.identifier === 'com.woltlab.wcf.search') {
-                                       data.handler.close(true);
-                                       
-                                       if (Environment.platform() === 'ios') {
-                                               scrollTop = document.body.scrollTop;
-                                               UiScreen.scrollDisable();
-                                       }
-                                       
-                                       _searchBar.style.setProperty('top', elById('pageHeader').offsetHeight + 'px', '');
-                                       _searchBar.classList.add('open');
-                                       _searchInput.focus();
-                                       
-                                       if (Environment.platform() === 'ios') {
-                                               document.body.scrollTop = 0;
-                                       }
-                               }
-                       });
-                       
-                       _main.addEventListener(WCF_CLICK_EVENT, function() {
-                               if (_searchBar) _searchBar.classList.remove('open');
-                               
-                               if (Environment.platform() === 'ios' && scrollTop !== null) {
-                                       UiScreen.scrollEnable();
-                                       document.body.scrollTop = scrollTop; 
-                                       
-                                       scrollTop = null;
-                               }
-                       });
-               },
-               
-               _initButtonGroupNavigation: function() {
-                       for (var i = 0, length = _buttonGroupNavigations.length; i < length; i++) {
-                               var navigation = _buttonGroupNavigations[i];
-                               
-                               if (navigation.classList.contains('jsMobileButtonGroupNavigation')) continue;
-                               else navigation.classList.add('jsMobileButtonGroupNavigation');
-                               
-                               var list = elBySel('.buttonList', navigation);
-                               if (list.childElementCount === 0) {
-                                       // ignore objects without options
-                                       continue;
-                               }
-                               
-                               navigation.parentNode.classList.add('hasMobileNavigation');
-                               
-                               var button = elCreate('a');
-                               button.className = 'dropdownLabel';
-                               
-                               var span = elCreate('span');
-                               span.className = 'icon icon24 fa-ellipsis-v';
-                               button.appendChild(span);
-                               
-                               (function(navigation, button, list) {
-                                       button.addEventListener(WCF_CLICK_EVENT, function(event) {
-                                               event.preventDefault();
-                                               event.stopPropagation();
-                                               
-                                               navigation.classList.toggle('open');
-                                       });
-                                       
-                                       list.addEventListener(WCF_CLICK_EVENT, function(event) {
-                                               event.stopPropagation();
-                                               
-                                               navigation.classList.remove('open');
-                                       });
-                               })(navigation, button, list);
-                               
-                               navigation.insertBefore(button, navigation.firstChild);
-                       }
-               },
-               
-               _initMessages: function() {
-                       Array.prototype.forEach.call(_messages, (function(message) {
-                               if (_knownMessages.has(message)) {
-                                       return;
-                               }
-                               
-                               var navigation = elBySel('.jsMobileNavigation', message);
-                               if (navigation) {
-                                       navigation.addEventListener(WCF_CLICK_EVENT, function(event) {
-                                               event.stopPropagation();
-                                               
-                                               // mimic dropdown behavior
-                                               window.setTimeout(function () {
-                                                       navigation.classList.remove('open');
-                                               }, 10);
-                                       });
-                                       
-                                       var quickOptions = elBySel('.messageQuickOptions', message);
-                                       if (quickOptions && navigation.childElementCount) {
-                                               quickOptions.classList.add('active');
-                                               quickOptions.addEventListener(WCF_CLICK_EVENT, (function (event) {
-                                                       if (_enabled && event.target.nodeName !== 'LABEL' && event.target.nodeName !== 'INPUT') {
-                                                               event.preventDefault();
-                                                               event.stopPropagation();
-                                                               
-                                                               this._toggleMobileNavigation(message, quickOptions, navigation);
-                                                       }
-                                               }).bind(this));
-                                       }
-                               }
-                               
-                               _knownMessages.add(message);
-                       }).bind(this));
-               },
-               
-               _initMobileMenu: function() {
-                       if (_options.enableMobileMenu) {
-                               _pageMenuMain = new UiPageMenuMain();
-                               _pageMenuUser = new UiPageMenuUser();
-                       }
-               },
-               
-               _closeAllMenus: function() {
-                       elBySelAll('.jsMobileButtonGroupNavigation.open, .jsMobileNavigation.open', null, function (menu) {
-                               menu.classList.remove('open');
-                       });
-                       
-                       if (_enabled && _dropdownMenu) _callbackCloseDropdown();
-               },
-               
-               rebuildShadow: function(elements, linkSelector) {
-                       var element, parent, shadow;
-                       for (var i = 0, length = elements.length; i < length; i++) {
-                               element = elements[i];
-                               parent = element.parentNode;
-                               
-                               shadow = DomTraverse.childByClass(parent, 'mobileLinkShadow');
-                               if (shadow === null) {
-                                       if (elBySel(linkSelector, element).href) {
-                                               shadow = elCreate('a');
-                                               shadow.className = 'mobileLinkShadow';
-                                               shadow.href = elBySel(linkSelector, element).href;
-                                               
-                                               parent.appendChild(shadow);
-                                               parent.classList.add('mobileLinkShadowContainer');
-                                       }
-                               }
-                       }
-               },
-               
-               removeShadow: function(elements) {
-                       var element, parent, shadow;
-                       for (var i = 0, length = elements.length; i < length; i++) {
-                               element = elements[i];
-                               parent = element.parentNode;
-                               
-                               if (parent.classList.contains('mobileLinkShadowContainer')) {
-                                       shadow = DomTraverse.childByClass(parent, 'mobileLinkShadow');
-                                       if (shadow !== null) {
-                                               elRemove(shadow);
-                                       }
-                                       
-                                       parent.classList.remove('mobileLinkShadowContainer');
-                               }
-                       }
-               },
-               
-               _enableSidebarXS: function() {
-                       _sidebarXsEnabled = true;
-               },
-               
-               _disableSidebarXS: function() {
-                       _sidebarXsEnabled = false;
-                       
-                       _sidebars.forEach(function (sidebar) {
-                               sidebar.classList.remove('open');
-                       });
-               },
-               
-               _setupSidebarXS: function() {
-                       _sidebars.forEach(function (sidebar) {
-                               sidebar.addEventListener('mousedown', function(event) {
-                                       if (_sidebarXsEnabled && event.target === sidebar) {
-                                               event.preventDefault();
-                                               
-                                               sidebar.classList.toggle('open');
-                                       }
-                               });
-                       });
-                       
-                       _sidebarXsEnabled = true;
-               },
-               
-               _toggleMobileNavigation: function (message, quickOptions, navigation) {
-                       if (_dropdownMenu === null) {
-                               _dropdownMenu = elCreate('ul');
-                               _dropdownMenu.className = 'dropdownMenu';
-                               
-                               UiDropdownReusable.init('com.woltlab.wcf.jsMobileNavigation', _dropdownMenu);
-                               
-                               _callbackCloseDropdown = function () {
-                                       _dropdownMenu.classList.remove('dropdownOpen');
-                               }
-                       }
-                       else if (_dropdownMenu.classList.contains('dropdownOpen')) {
-                               _callbackCloseDropdown();
-                               
-                               if (_dropdownMenuMessage === message) {
-                                       // toggle behavior
-                                       return;
-                               }
-                       }
-                       
-                       _dropdownMenu.innerHTML = '';
-                       UiCloseOverlay.execute();
-                       
-                       this._rebuildMobileNavigation(navigation);
-                       
-                       var previousNavigation = navigation.previousElementSibling;
-                       if (previousNavigation && previousNavigation.classList.contains('messageFooterButtonsExtra')) {
-                               var divider = elCreate('li');
-                               divider.className = 'dropdownDivider';
-                               _dropdownMenu.appendChild(divider);
-                               
-                               this._rebuildMobileNavigation(previousNavigation);
-                       }
-                       
-                       UiAlignment.set(_dropdownMenu, quickOptions, {
-                               horizontal: 'right',
-                               allowFlip: 'vertical'
-                       });
-                       _dropdownMenu.classList.add('dropdownOpen');
-                       
-                       _dropdownMenuMessage = message;
-               },
-               
-               _rebuildMobileNavigation: function (navigation) {
-                       elBySelAll('.button:not(.ignoreMobileNavigation)', navigation, function (button) {
-                               var item = elCreate('li');
-                               if (button.classList.contains('active')) item.className = 'active';
-                               item.innerHTML = '<a href="#">' + elBySel('span:not(.icon)', button).textContent + '</a>';
-                               item.children[0].addEventListener(WCF_CLICK_EVENT, function (event) {
-                                       event.preventDefault();
-                                       event.stopPropagation();
-                                       
-                                       if (button.nodeName === 'A') button.click();
-                                       else Core.triggerEvent(button, WCF_CLICK_EVENT);
-                                       
-                                       _callbackCloseDropdown();
-                               });
-                               
-                               _dropdownMenu.appendChild(item);
-                       });
-               }
-       };
-});
-
-/**
- * Simple tab menu implementation with a straight-forward logic.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/TabMenu/Simple
- */
-define('WoltLabSuite/Core/Ui/TabMenu/Simple',['Dictionary', 'EventHandler', 'Dom/Traverse', 'Dom/Util'], function(Dictionary, EventHandler, DomTraverse, DomUtil) {
-       "use strict";
-       
-       /**
-        * @param       {Element}       container       container element
-        * @constructor
-        */
-       function TabMenuSimple(container) {
-               this._container = container;
-               this._containers = new Dictionary();
-               this._isLegacy = null;
-               this._store = null;
-               this._tabs = new Dictionary();
-       }
-       
-       TabMenuSimple.prototype = {
-               /**
-                * Validates the properties and DOM structure of this container.
-                * 
-                * Expected DOM:
-                * <div class="tabMenuContainer">
-                *      <nav>
-                *              <ul>
-                *                      <li data-name="foo"><a>bar</a></li>
-                *              </ul>
-                *      </nav>
-                *      
-                *      <div id="foo">baz</div>
-                * </div>
-                * 
-                * @return      {boolean}       false if any properties are invalid or the DOM does not match the expectations
-                */
-               validate: function() {
-                       if (!this._container.classList.contains('tabMenuContainer')) {
-                               return false;
-                       }
-                       
-                       var nav = DomTraverse.childByTag(this._container, 'NAV');
-                       if (nav === null) {
-                               return false;
-                       }
-                       
-                       // get children
-                       var tabs = elByTag('li', nav);
-                       if (tabs.length === 0) {
-                               return false;
-                       }
-                       
-                       var container, containers = DomTraverse.childrenByTag(this._container, 'DIV'), name, i, length;
-                       for (i = 0, length = containers.length; i < length; i++) {
-                               container = containers[i];
-                               name = elData(container, 'name');
-                               
-                               if (!name) {
-                                       name = DomUtil.identify(container);
-                               }
-                               
-                               elData(container, 'name', name);
-                               this._containers.set(name, container);
-                       }
-                       
-                       var containerId = this._container.id, tab;
-                       for (i = 0, length = tabs.length; i < length; i++) {
-                               tab = tabs[i];
-                               name = this._getTabName(tab);
-                               
-                               if (!name) {
-                                       continue;
-                               }
-                               
-                               if (this._tabs.has(name)) {
-                                       throw new Error("Tab names must be unique, li[data-name='" + name + "'] (tab menu id: '" + containerId + "') exists more than once.");
-                               }
-                               
-                               container = this._containers.get(name);
-                               if (container === undefined) {
-                                       throw new Error("Expected content element for li[data-name='" + name + "'] (tab menu id: '" + containerId + "').");
-                               }
-                               else if (container.parentNode !== this._container) {
-                                       throw new Error("Expected content element '" + name + "' (tab menu id: '" + containerId + "') to be a direct children.");
-                               }
-                               
-                               // check if tab holds exactly one children which is an anchor element
-                               if (tab.childElementCount !== 1 || tab.children[0].nodeName !== 'A') {
-                                       throw new Error("Expected exactly one <a> as children for li[data-name='" + name + "'] (tab menu id: '" + containerId + "').");
-                               }
-                               
-                               this._tabs.set(name, tab);
-                       }
-                       
-                       if (!this._tabs.size) {
-                               throw new Error("Expected at least one tab (tab menu id: '" + containerId + "').");
-                       }
-                       
-                       if (this._isLegacy) {
-                               elData(this._container, 'is-legacy', true);
-                               
-                               this._tabs.forEach(function(tab, name) {
-                                       elAttr(tab, 'aria-controls', name);
-                               });
-                       }
-                       
-                       return true;
-               },
-               
-               /**
-                * Initializes this tab menu.
-                * 
-                * @param       {Dictionary=}   oldTabs         previous list of tabs
-                * @return      {?Element}      parent tab for selection or null
-                */
-               init: function(oldTabs) {
-                       oldTabs = oldTabs || null;
-                       
-                       // bind listeners
-                       this._tabs.forEach((function(tab) {
-                               if (!oldTabs || oldTabs.get(elData(tab, 'name')) !== tab) {
-                                       tab.children[0].addEventListener(WCF_CLICK_EVENT, this._onClick.bind(this));
-                               }
-                       }).bind(this));
-                       
-                       var returnValue = null;
-                       if (!oldTabs) {
-                               var hash = TabMenuSimple.getIdentifierFromHash();
-                               var selectTab = null;
-                               if (hash !== '') {
-                                       selectTab = this._tabs.get(hash);
-                                       
-                                       // check for parent tab menu
-                                       if (selectTab && this._container.parentNode.classList.contains('tabMenuContainer')) {
-                                               returnValue = this._container;
-                                       }
-                               }
-                               
-                               if (!selectTab) {
-                                       var preselect = elData(this._container, 'preselect') || elData(this._container, 'active');
-                                       if (preselect === "true" || !preselect) preselect = true;
-                                       
-                                       if (preselect === true) {
-                                               this._tabs.forEach(function(tab) {
-                                                       if (!selectTab && !elIsHidden(tab) && (!tab.previousElementSibling || elIsHidden(tab.previousElementSibling))) {
-                                                               selectTab = tab;
-                                                       }
-                                               });
-                                       }
-                                       else if (preselect !== "false") {
-                                               selectTab = this._tabs.get(preselect);
-                                       }
-                               }
-                               
-                               if (selectTab) {
-                                       this._containers.forEach(function(container) {
-                                               container.classList.add('hidden');
-                                       });
-                                       
-                                       this.select(null, selectTab, true);
-                               }
-                               
-                               var store = elData(this._container, 'store');
-                               if (store) {
-                                       var input = elCreate('input');
-                                       input.type = 'hidden';
-                                       input.name = store;
-                                       input.value = elData(this.getActiveTab(), 'name');
-                                       
-                                       this._container.appendChild(input);
-                                       
-                                       this._store = input;
-                               }
-                       }
-                       
-                       return returnValue;
-               },
-               
-               /**
-                * Selects a tab.
-                * 
-                * @param       {?(string|int)}         name            tab name or sequence no
-                * @param       {Element=}              tab             tab element
-                * @param       {boolean=}              disableEvent    suppress event handling
-                */
-               select: function(name, tab, disableEvent) {
-                       tab = tab || this._tabs.get(name);
-                       
-                       if (!tab) {
-                               // check if name is an integer
-                               if (~~name == name) {
-                                       name = ~~name;
-                                       
-                                       var i = 0;
-                                       this._tabs.forEach(function(item) {
-                                               if (i === name) {
-                                                       tab = item;
-                                               }
-                                               
-                                               i++;
-                                       });
-                               }
-                               
-                               if (!tab) {
-                                       throw new Error("Expected a valid tab name, '" + name + "' given (tab menu id: '" + this._container.id + "').");
-                               }
-                       }
-                       
-                       name = name || elData(tab, 'name');
-                       
-                       // unmark active tab
-                       var oldTab = this.getActiveTab();
-                       var oldContent = null;
-                       if (oldTab) {
-                               var oldTabName = elData(oldTab, 'name');
-                               if (oldTabName === name) {
-                                       // same tab
-                                       return;
-                               }
-                               
-                               if (!disableEvent) {
-                                       EventHandler.fire('com.woltlab.wcf.simpleTabMenu_' + this._container.id, 'beforeSelect', {
-                                               tab: oldTab,
-                                               tabName: oldTabName
-                                       });
-                               }
-                               
-                               oldTab.classList.remove('active');
-                               oldContent = this._containers.get(elData(oldTab, 'name'));
-                               oldContent.classList.remove('active');
-                               oldContent.classList.add('hidden');
-                               
-                               if (this._isLegacy) {
-                                       oldTab.classList.remove('ui-state-active');
-                                       oldContent.classList.remove('ui-state-active');
-                               }
-                       }
-                       
-                       tab.classList.add('active');
-                       var newContent = this._containers.get(name);
-                       newContent.classList.add('active');
-                       newContent.classList.remove('hidden');
-                       
-                       if (this._isLegacy) {
-                               tab.classList.add('ui-state-active');
-                               newContent.classList.add('ui-state-active');
-                       }
-                       
-                       if (this._store) {
-                               this._store.value = name;
-                       }
-                       
-                       if (!disableEvent) {
-                               EventHandler.fire('com.woltlab.wcf.simpleTabMenu_' + this._container.id, 'select', {
-                                       active: tab,
-                                       activeName: name,
-                                       previous: oldTab,
-                                       previousName: oldTab ? elData(oldTab, 'name') : null
-                               });
-                               
-                               var jQuery = (this._isLegacy && typeof window.jQuery === 'function') ? window.jQuery : null;
-                               if (jQuery) {
-                                       // simulate jQuery UI Tabs event
-                                       jQuery(this._container).trigger('wcftabsbeforeactivate', {
-                                               newTab: jQuery(tab),
-                                               oldTab: jQuery(oldTab),
-                                               newPanel: jQuery(newContent),
-                                               oldPanel: jQuery(oldContent)
-                                       });
-                               }
-                               
-                               var location = window.location.href.replace(/#+[^#]*$/, '');
-                               if (TabMenuSimple.getIdentifierFromHash() === name) {
-                                       location += window.location.hash;
-                               }
-                               else {
-                                       location += '#' + name;
-                               }
-                               
-                               // update history
-                               //noinspection JSCheckFunctionSignatures
-                               window.history.replaceState(
-                                       undefined,
-                                       undefined,
-                                       location
-                               );
-                       }
-                       
-                       require(['WoltLabSuite/Core/Ui/TabMenu'], function (UiTabMenu) {
-                               //noinspection JSUnresolvedFunction
-                               UiTabMenu.scrollToTab(tab);
-                       });
-               },
-               
-               /**
-                * Selects the first visible tab of the tab menu and return `true`. If there is no
-                * visible tab, `false` is returned.
-                * 
-                * The visibility of a tab is determined by calling `elIsHidden` with the tab menu
-                * item as the parameter.
-                *
-                * @return      {boolean}
-                */
-               selectFirstVisible: function() {
-                       var selectTab;
-                       this._tabs.forEach(function(tab) {
-                               if (!selectTab && !elIsHidden(tab)) {
-                                       selectTab = tab;
-                               }
-                       }.bind(this));
-                       
-                       if (selectTab) {
-                               this.select(undefined, selectTab, false);
-                       }
-                       
-                       return !!selectTab;
-               },
-               
-               /**
-                * Rebuilds all tabs, must be invoked after adding or removing of tabs.
-                * 
-                * Warning: Do not remove tabs if you plan to add these later again or at least clone the nodes
-                *          to prevent issues with already bound event listeners. Consider hiding them via CSS.
-                */
-               rebuild: function() {
-                       var oldTabs = new Dictionary();
-                       oldTabs.merge(this._tabs);
-                       
-                       this.validate();
-                       this.init(oldTabs);
-               },
-               
-               /**
-                * Returns true if this tab menu has a tab with provided name.
-                * 
-                * @param       {string}        name    tab name
-                * @return      {boolean}       true if tab name matches
-                */
-               hasTab: function (name) {
-                       return this._tabs.has(name);
-               },
-               
-               /**
-                * Handles clicks on a tab.
-                * 
-                * @param       {object}        event   event object
-                */
-               _onClick: function(event) {
-                       event.preventDefault();
-                       
-                       this.select(null, event.currentTarget.parentNode);
-               },
-               
-               /**
-                * Returns the tab name.
-                * 
-                * @param       {Element}       tab     tab element
-                * @return      {string}        tab name
-                */
-               _getTabName: function(tab) {
-                       var name = elData(tab, 'name');
-                       
-                       // handle legacy tab menus
-                       if (!name) {
-                               if (tab.childElementCount === 1 && tab.children[0].nodeName === 'A') {
-                                       if (tab.children[0].href.match(/#([^#]+)$/)) {
-                                               name = RegExp.$1;
-                                               
-                                               if (elById(name) === null) {
-                                                       name = null;
-                                               }
-                                               else {
-                                                       this._isLegacy = true;
-                                                       elData(tab, 'name', name);
-                                               }
-                                       }
-                               }
-                       }
-                       
-                       return name;
-               },
-               
-               /**
-                * Returns the currently active tab.
-                *
-                * @return      {Element}       active tab
-                */
-               getActiveTab: function() {
-                       return elBySel('#' + this._container.id + ' > nav > ul > li.active');
-               },
-               
-               /**
-                * Returns the list of registered content containers.
-                * 
-                * @returns     {Dictionary}    content containers
-                */
-               getContainers: function() {
-                       return this._containers;
-               },
-               
-               /**
-                * Returns the list of registered tabs.
-                * 
-                * @returns     {Dictionary}    tab items
-                */
-               getTabs: function() {
-                       return this._tabs;
-               }
-       };
-       
-       TabMenuSimple.getIdentifierFromHash = function () {
-               if (window.location.hash.match(/^#+([^\/]+)+(?:\/.+)?/)) {
-                       return RegExp.$1;
-               }
-               
-               return '';
-       };
-       
-       return TabMenuSimple;
-});
-
-/**
- * Common interface for tab menu access.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/TabMenu
- */
-define('WoltLabSuite/Core/Ui/TabMenu',['Dictionary', 'EventHandler', 'Dom/ChangeListener', 'Dom/Util', 'Ui/CloseOverlay', 'Ui/Screen', './TabMenu/Simple'], function(Dictionary, EventHandler, DomChangeListener, DomUtil, UiCloseOverlay, UiScreen, SimpleTabMenu) {
-       "use strict";
-       
-       var _activeList = null;
-       var _enableTabScroll = false;
-       var _tabMenus = new Dictionary();
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/TabMenu
-        */
-       return {
-               /**
-                * Sets up tab menus and binds listeners.
-                */
-               setup: function() {
-                       this._init();
-                       this._selectErroneousTabs();
-                       
-                       DomChangeListener.add('WoltLabSuite/Core/Ui/TabMenu', this._init.bind(this));
-                       UiCloseOverlay.add('WoltLabSuite/Core/Ui/TabMenu', function() {
-                               if (_activeList) {
-                                       _activeList.classList.remove('active');
-                                       
-                                       _activeList = null;
-                               }
-                       });
-                       
-                       //noinspection JSUnresolvedVariable
-                       UiScreen.on('screen-sm-down', {
-                               enable: this._scrollEnable.bind(this, false),
-                               disable: this._scrollDisable.bind(this),
-                               setup: this._scrollEnable.bind(this, true)
-                       });
-                       
-                       window.addEventListener('hashchange', function () {
-                               var hash = SimpleTabMenu.getIdentifierFromHash();
-                               var element = (hash) ? elById(hash) : null;
-                               if (element !== null && element.classList.contains('tabMenuContent')) {
-                                       _tabMenus.forEach(function (tabMenu) {
-                                               if (tabMenu.hasTab(hash)) {
-                                                       tabMenu.select(hash);
-                                               }
-                                       });
-                               }
-                       });
-                       
-                       var hash = SimpleTabMenu.getIdentifierFromHash();
-                       if (hash) {
-                               window.setTimeout(function () {
-                                       // check if page was initially scrolled using a tab id
-                                       var tabMenuContent = elById(hash);
-                                       if (tabMenuContent && tabMenuContent.classList.contains('tabMenuContent')) {
-                                               var scrollY = (window.scrollY || window.pageYOffset);
-                                               if (scrollY > 0) {
-                                                       var parent = tabMenuContent.parentNode;
-                                                       var offsetTop = parent.offsetTop - 50;
-                                                       if (offsetTop < 0) offsetTop = 0;
-                                                       
-                                                       if (scrollY > offsetTop) {
-                                                               var y = DomUtil.offset(parent).top;
-                                                               
-                                                               if (y <= 50) {
-                                                                       y = 0;
-                                                               }
-                                                               else {
-                                                                       y -= 50;
-                                                               }
-                                                               
-                                                               window.scrollTo(0, y);
-                                                       }
-                                               }
-                                       }
-                               }, 100);
-                       }
-               },
-               
-               /**
-                * Initializes available tab menus.
-                */
-               _init: function() {
-                       var container, containerId, list, returnValue, tabMenu, tabMenus = elBySelAll('.tabMenuContainer:not(.staticTabMenuContainer)');
-                       for (var i = 0, length = tabMenus.length; i < length; i++) {
-                               container = tabMenus[i];
-                               containerId = DomUtil.identify(container);
-                               
-                               if (_tabMenus.has(containerId)) {
-                                       continue;
-                               }
-                               
-                               tabMenu = new SimpleTabMenu(container);
-                               if (tabMenu.validate()) {
-                                       returnValue = tabMenu.init();
-                                       
-                                       _tabMenus.set(containerId, tabMenu);
-                                       
-                                       if (returnValue instanceof Element) {
-                                               tabMenu = this.getTabMenu(returnValue.parentNode.id);
-                                               tabMenu.select(returnValue.id, null, true);
-                                       }
-                                       
-                                       list = elBySel('#' + containerId + ' > nav > ul');
-                                       (function(list) {
-                                               list.addEventListener(WCF_CLICK_EVENT, function(event) {
-                                                       event.preventDefault();
-                                                       event.stopPropagation();
-                                                       
-                                                       if (event.target === list) {
-                                                               list.classList.add('active');
-                                                               
-                                                               _activeList = list;
-                                                       }
-                                                       else {
-                                                               list.classList.remove('active');
-                                                               
-                                                               _activeList = null;
-                                                       }
-                                               });
-                                       })(list);
-                                       
-                                       // bind scroll listener
-                                       elBySelAll('.tabMenu, .menu', container, (function(menu) {
-                                               var callback = this._rebuildMenuOverflow.bind(this, menu);
-                                               
-                                               var timeout = null;
-                                               elBySel('ul', menu).addEventListener('scroll', function () {
-                                                       if (timeout !== null) {
-                                                               window.clearTimeout(timeout);
-                                                       }
-                                                       
-                                                       // slight delay to avoid calling this function too often
-                                                       timeout = window.setTimeout(callback, 10);
-                                               });
-                                       }).bind(this));
-                               }
-                       }
-               },
-               
-               /**
-                * Selects the first tab containing an element with class `formError`.
-                */
-               _selectErroneousTabs: function() {
-                       _tabMenus.forEach(function(tabMenu) {
-                               var foundError = false;
-                               tabMenu.getContainers().forEach(function(container) {
-                                       if (!foundError && elByClass('formError', container).length) {
-                                               foundError = true;
-                                               
-                                               tabMenu.select(container.id);
-                                       }
-                               });
-                       });
-               },
-               
-               /**
-                * Returns a SimpleTabMenu instance for given container id.
-                * 
-                * @param       {string}        containerId     tab menu container id
-                * @return      {(SimpleTabMenu|undefined)}     tab menu object
-                */
-               getTabMenu: function(containerId) {
-                       return _tabMenus.get(containerId);
-               },
-               
-               _scrollEnable: function (isSetup) {
-                       _enableTabScroll = true;
-                       
-                       _tabMenus.forEach((function (tabMenu) {
-                               var activeTab = tabMenu.getActiveTab();
-                               if (isSetup) {
-                                       this._rebuildMenuOverflow(activeTab.closest('.menu, .tabMenu'));
-                               }
-                               else {
-                                       this.scrollToTab(activeTab);
-                               }
-                       }).bind(this));
-               },
-               
-               _scrollDisable: function () {
-                       _enableTabScroll = false;
-               },
-               
-               scrollToTab: function (tab) {
-                       if (!_enableTabScroll) {
-                               return;
-                       }
-                       
-                       var list = tab.closest('ul');
-                       var width = list.clientWidth;
-                       var scrollLeft = list.scrollLeft;
-                       var scrollWidth = list.scrollWidth;
-                       if (width === scrollWidth) {
-                               // no overflow, ignore
-                               return;
-                       }
-                       
-                       // check if tab is currently visible
-                       var left = tab.offsetLeft;
-                       var shouldScroll = false;
-                       if (left < scrollLeft) {
-                               shouldScroll = true;
-                       }
-                       
-                       var paddingRight = false;
-                       if (!shouldScroll) {
-                               var visibleWidth = width - (left - scrollLeft);
-                               var virtualWidth = tab.clientWidth;
-                               if (tab.nextElementSibling !== null) {
-                                       paddingRight = true;
-                                       virtualWidth += 20;
-                               }
-                               
-                               if (visibleWidth < virtualWidth) {
-                                       shouldScroll = true;
-                               }
-                       }
-                       
-                       if (shouldScroll) {
-                               this._scrollMenu(list, left, scrollLeft, scrollWidth, width, paddingRight);
-                       }
-               },
-               
-               _scrollMenu: function (list, left, scrollLeft, scrollWidth, width, paddingRight) {
-                       // allow some padding to indicate overflow
-                       if (paddingRight) {
-                               left -= 15;
-                       }
-                       else if (left > 0) {
-                               left -= 15;
-                       }
-                       
-                       if (left < 0) {
-                               left = 0;
-                       }
-                       else {
-                               // ensure that our left value is always within the boundaries
-                               left = Math.min(left, scrollWidth - width);
-                       }
-                       
-                       if (scrollLeft === left) {
-                               return;
-                       }
-                       
-                       list.classList.add('enableAnimation');
-                       
-                       // new value is larger, we're scrolling towards the end
-                       if (scrollLeft < left) {
-                               list.firstElementChild.style.setProperty('margin-left', (scrollLeft - left) + 'px', '');
-                       }
-                       else {
-                               // new value is smaller, we're scrolling towards the start
-                               list.style.setProperty('padding-left', (scrollLeft - left) + 'px', '');
-                       }
-                       
-                       setTimeout(function () {
-                               list.classList.remove('enableAnimation');
-                               
-                               list.firstElementChild.style.removeProperty('margin-left');
-                               list.style.removeProperty('padding-left');
-                               
-                               list.scrollLeft = left;
-                       }, 300);
-               },
-               
-               _rebuildMenuOverflow: function (menu) {
-                       if (!_enableTabScroll) {
-                               return;
-                       }
-                       
-                       var width = menu.clientWidth;
-                       var list = elBySel('ul', menu);
-                       var scrollLeft = list.scrollLeft;
-                       var scrollWidth = list.scrollWidth;
-                       
-                       var overflowLeft = (scrollLeft > 0);
-                       var overlayLeft = elBySel('.tabMenuOverlayLeft', menu);
-                       if (overflowLeft) {
-                               if (overlayLeft === null) {
-                                       overlayLeft = elCreate('span');
-                                       overlayLeft.className = 'tabMenuOverlayLeft icon icon24 fa-angle-left';
-                                       overlayLeft.addEventListener(WCF_CLICK_EVENT, (function () {
-                                               var listWidth = list.clientWidth;
-                                               
-                                               this._scrollMenu(
-                                                       list,
-                                                       list.scrollLeft - ~~(listWidth / 2),
-                                                       list.scrollLeft,
-                                                       list.scrollWidth,
-                                                       listWidth,
-                                                       0
-                                               );
-                                       }).bind(this));
-                                       
-                                       menu.insertBefore(overlayLeft, menu.firstChild);
-                               }
-                               
-                               overlayLeft.classList.add('active');
-                       }
-                       else if (overlayLeft !== null) {
-                               overlayLeft.classList.remove('active');
-                       }
-                       
-                       var overflowRight = (width + scrollLeft < scrollWidth);
-                       var overlayRight = elBySel('.tabMenuOverlayRight', menu);
-                       if (overflowRight) {
-                               if (overlayRight === null) {
-                                       overlayRight = elCreate('span');
-                                       overlayRight.className = 'tabMenuOverlayRight icon icon24 fa-angle-right';
-                                       overlayRight.addEventListener(WCF_CLICK_EVENT, (function () {
-                                               var listWidth = list.clientWidth;
-                                               
-                                               this._scrollMenu(
-                                                       list,
-                                                       list.scrollLeft + ~~(listWidth / 2),
-                                                       list.scrollLeft,
-                                                       list.scrollWidth,
-                                                       listWidth,
-                                                       0
-                                               );
-                                       }).bind(this));
-                                       
-                                       menu.appendChild(overlayRight);
-                               }
-                               
-                               overlayRight.classList.add('active');
-                       }
-                       else if (overlayRight !== null) {
-                               overlayRight.classList.remove('active');
-                       }
-               }
-       };
-});
-
-/**
- * Dynamically transforms menu-like structures to handle items exceeding the available width
- * by moving them into a separate dropdown.  
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/FlexibleMenu
- */
-define('WoltLabSuite/Core/Ui/FlexibleMenu',['Core', 'Dictionary', 'Dom/ChangeListener', 'Dom/Traverse', 'Dom/Util', 'Ui/SimpleDropdown'], function(Core, Dictionary, DomChangeListener, DomTraverse, DomUtil, SimpleDropdown) {
-       "use strict";
-       
-       var _containers = new Dictionary();
-       var _dropdowns = new Dictionary();
-       var _dropdownMenus = new Dictionary();
-       var _itemLists = new Dictionary();
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/FlexibleMenu
-        */
-       var UiFlexibleMenu = {
-               /**
-                * Register default menus and set up event listeners.
-                */
-               setup: function() {
-                       if (elById('mainMenu') !== null) this.register('mainMenu');
-                       var navigationHeader = elBySel('.navigationHeader');
-                       if (navigationHeader !== null) this.register(DomUtil.identify(navigationHeader));
-                       
-                       window.addEventListener('resize', this.rebuildAll.bind(this));
-                       DomChangeListener.add('WoltLabSuite/Core/Ui/FlexibleMenu', this.registerTabMenus.bind(this));
-               },
-               
-               /**
-                * Registers a menu by element id.
-                * 
-                * @param       {string}        containerId     element id
-                */
-               register: function(containerId) {
-                       var container = elById(containerId);
-                       if (container === null) {
-                               throw "Expected a valid element id, '" + containerId + "' does not exist.";
-                       }
-                       
-                       if (_containers.has(containerId)) {
-                               return;
-                       }
-                       
-                       var list = DomTraverse.childByTag(container, 'UL');
-                       if (list === null) {
-                               throw "Expected an <ul> element as child of container '" + containerId + "'.";
-                       }
-                       
-                       _containers.set(containerId, container);
-                       _itemLists.set(containerId, list);
-                       
-                       this.rebuild(containerId);
-               },
-               
-               /**
-                * Registers tab menus.
-                */
-               registerTabMenus: function() {
-                       var tabMenus = elBySelAll('.tabMenuContainer:not(.jsFlexibleMenuEnabled), .messageTabMenu:not(.jsFlexibleMenuEnabled)');
-                       for (var i = 0, length = tabMenus.length; i < length; i++) {
-                               var tabMenu = tabMenus[i];
-                               var nav = DomTraverse.childByTag(tabMenu, 'NAV');
-                               if (nav !== null) {
-                                       tabMenu.classList.add('jsFlexibleMenuEnabled');
-                                       this.register(DomUtil.identify(nav));
-                               }
-                       }
-               },
-               
-               /**
-                * Rebuilds all menus, e.g. on window resize.
-                */
-               rebuildAll: function() {
-                       _containers.forEach((function(container, containerId) {
-                               this.rebuild(containerId);
-                       }).bind(this));
-               },
-               
-               /**
-                * Rebuild the menu identified by given element id.
-                * 
-                * @param       {string}        containerId     element id
-                */
-               rebuild: function(containerId) {
-                       var container = _containers.get(containerId);
-                       if (container === undefined) {
-                               throw "Expected a valid element id, '" + containerId + "' is unknown.";
-                       }
-                       
-                       var styles = window.getComputedStyle(container);
-                       
-                       var availableWidth = container.parentNode.clientWidth;
-                       availableWidth -= DomUtil.styleAsInt(styles, 'margin-left');
-                       availableWidth -= DomUtil.styleAsInt(styles, 'margin-right');
-                       
-                       var list = _itemLists.get(containerId);
-                       var items = DomTraverse.childrenByTag(list, 'LI');
-                       var dropdown = _dropdowns.get(containerId);
-                       var dropdownWidth = 0;
-                       if (dropdown !== undefined) {
-                               // show all items for calculation
-                               for (var i = 0, length = items.length; i < length; i++) {
-                                       var item = items[i];
-                                       if (item.classList.contains('dropdown')) {
-                                               continue;
-                                       }
-                                       
-                                       elShow(item);
-                               }
-                               
-                               if (dropdown.parentNode !== null) {
-                                       dropdownWidth = DomUtil.outerWidth(dropdown);
-                               }
-                       }
-                       
-                       var currentWidth = list.scrollWidth - dropdownWidth;
-                       var hiddenItems = [];
-                       if (currentWidth > availableWidth) {
-                               // hide items starting with the last one
-                               for (var i = items.length - 1; i >= 0; i--) {
-                                       var item = items[i];
-                                       
-                                       // ignore dropdown and active item
-                                       if (item.classList.contains('dropdown') || item.classList.contains('active') || item.classList.contains('ui-state-active')) {
-                                               continue;
-                                       }
-                                       
-                                       hiddenItems.push(item);
-                                       elHide(item);
-                                       
-                                       if (list.scrollWidth < availableWidth) {
-                                               break;
-                                       }
-                               }
-                       }
-                       
-                       if (hiddenItems.length) {
-                               var dropdownMenu;
-                               if (dropdown === undefined) {
-                                       dropdown = elCreate('li');
-                                       dropdown.className = 'dropdown jsFlexibleMenuDropdown';
-                                       var icon = elCreate('a');
-                                       icon.className = 'icon icon16 fa-list';
-                                       dropdown.appendChild(icon);
-                                       
-                                       dropdownMenu = elCreate('ul');
-                                       dropdownMenu.classList.add('dropdownMenu');
-                                       dropdown.appendChild(dropdownMenu);
-                                       
-                                       _dropdowns.set(containerId, dropdown);
-                                       _dropdownMenus.set(containerId, dropdownMenu);
-                                       
-                                       SimpleDropdown.init(icon);
-                               }
-                               else {
-                                       dropdownMenu = _dropdownMenus.get(containerId);
-                               }
-                               
-                               if (dropdown.parentNode === null) {
-                                       list.appendChild(dropdown);
-                               }
-                               
-                               // build dropdown menu
-                               var fragment = document.createDocumentFragment();
-                               
-                               var self = this;
-                               hiddenItems.forEach(function(hiddenItem) {
-                                       var item = elCreate('li');
-                                       item.innerHTML = hiddenItem.innerHTML;
-                                       
-                                       item.addEventListener(WCF_CLICK_EVENT, (function(event) {
-                                               event.preventDefault();
-                                               
-                                               Core.triggerEvent(elBySel('a', hiddenItem), WCF_CLICK_EVENT);
-                                               
-                                               // force a rebuild to guarantee the active item being visible
-                                               setTimeout(function() {
-                                                       self.rebuild(containerId);
-                                               }, 59);
-                                       }).bind(this));
-                                       
-                                       fragment.appendChild(item);
-                               });
-                               
-                               dropdownMenu.innerHTML = '';
-                               dropdownMenu.appendChild(fragment);
-                       }
-                       else if (dropdown !== undefined && dropdown.parentNode !== null) {
-                               elRemove(dropdown);
-                       }
-               }
-       };
-       
-       return UiFlexibleMenu;
-});
-
-/**
- * Provides enhanced tooltips.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Tooltip
- */
-define('WoltLabSuite/Core/Ui/Tooltip',['Environment', 'Dom/ChangeListener', 'Ui/Alignment'], function(Environment, DomChangeListener, UiAlignment) {
-       "use strict";
-       
-       var _callbackMouseEnter = null;
-       var _callbackMouseLeave = null;
-       var _elements = null;
-       var _pointer = null;
-       var _text = null;
-       var _tooltip = null;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Tooltip
-        */
-       return {
-               /**
-                * Initializes the tooltip element and binds event listener.
-                */
-               setup: function() {
-                       if (Environment.platform() !== 'desktop') return;
-                       
-                       _tooltip = elCreate('div');
-                       elAttr(_tooltip, 'id', 'balloonTooltip');
-                       _tooltip.classList.add('balloonTooltip');
-                       _tooltip.addEventListener('transitionend', function () {
-                               if (!_tooltip.classList.contains('active')) {
-                                       // reset back to the upper left corner, prevent it from staying outside
-                                       // the viewport if the body overflow was previously hidden
-                                       ['bottom', 'left', 'right', 'top'].forEach(function(property) {
-                                               _tooltip.style.removeProperty(property);
-                                       });
-                               }
-                       });
-                       
-                       _text = elCreate('span');
-                       elAttr(_text, 'id', 'balloonTooltipText');
-                       _tooltip.appendChild(_text);
-                       
-                       _pointer = elCreate('span');
-                       _pointer.classList.add('elementPointer');
-                       _pointer.appendChild(elCreate('span'));
-                       _tooltip.appendChild(_pointer);
-                       
-                       document.body.appendChild(_tooltip);
-                       
-                       _elements = elByClass('jsTooltip');
-                       
-                       _callbackMouseEnter = this._mouseEnter.bind(this);
-                       _callbackMouseLeave = this._mouseLeave.bind(this);
-                       
-                       this.init();
-                       
-                       DomChangeListener.add('WoltLabSuite/Core/Ui/Tooltip', this.init.bind(this));
-                       window.addEventListener('scroll', this._mouseLeave.bind(this));
-               },
-               
-               /**
-                * Initializes tooltip elements.
-                */
-               init: function() {
-                       if (_elements.length === 0) {
-                               return;
-                       }
-                       
-                       elBySelAll('.jsTooltip', undefined, function (element) {
-                               element.classList.remove('jsTooltip');
-                               
-                               var title = elAttr(element, 'title').trim();
-                               if (title.length) {
-                                       elData(element, 'tooltip', title);
-                                       element.removeAttribute('title');
-                                       elAttr(element, 'aria-label', title);
-                                       
-                                       element.addEventListener('mouseenter', _callbackMouseEnter);
-                                       element.addEventListener('mouseleave', _callbackMouseLeave);
-                                       element.addEventListener(WCF_CLICK_EVENT, _callbackMouseLeave);
-                               }
-                       });
-               },
-               
-               /**
-                * Displays the tooltip on mouse enter.
-                * 
-                * @param       {Event}         event   event object
-                */
-               _mouseEnter: function(event) {
-                       var element = event.currentTarget;
-                       var title = elAttr(element, 'title');
-                       title = (typeof title === 'string') ? title.trim() : '';
-                       
-                       if (title !== '') {
-                               elData(element, 'tooltip', title);
-                               element.removeAttribute('title');
-                       }
-                       
-                       title = elData(element, 'tooltip');
-                       
-                       // reset tooltip position
-                       _tooltip.style.removeProperty('top');
-                       _tooltip.style.removeProperty('left');
-                       
-                       // ignore empty tooltip
-                       if (!title.length) {
-                               _tooltip.classList.remove('active');
-                               return;
-                       }
-                       else {
-                               _tooltip.classList.add('active');
-                       }
-                       
-                       _text.textContent = title;
-                       
-                       UiAlignment.set(_tooltip, element, {
-                               horizontal: 'center',
-                               verticalOffset: 4,
-                               pointer: true,
-                               pointerClassNames: ['inverse'],
-                               vertical: 'top'
-                       });
-               },
-               
-               /**
-                * Hides the tooltip once the mouse leaves the element.
-                */
-               _mouseLeave: function() {
-                       _tooltip.classList.remove('active');
-               }
-       };
-});
-
-/**
- * Date picker with time support.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Date/Picker
- */
-define('WoltLabSuite/Core/Date/Picker',['DateUtil', 'Dom/Traverse', 'Dom/Util', 'EventHandler', 'Language', 'ObjectMap', 'Dom/ChangeListener', 'Ui/Alignment', 'WoltLabSuite/Core/Ui/CloseOverlay'], function(DateUtil, DomTraverse, DomUtil, EventHandler, Language, ObjectMap, DomChangeListener, UiAlignment, UiCloseOverlay) {
-       "use strict";
-       
-       var _didInit = false;
-       var _firstDayOfWeek = 0;
-       var _wasInsidePicker = false;
-       
-       var _data = new ObjectMap();
-       var _input = null;
-       var _maxDate = 0;
-       var _minDate = 0;
-       
-       var _dateCells = [];
-       var _dateGrid = null;
-       var _dateHour = null;
-       var _dateMinute = null;
-       var _dateMonth = null;
-       var _dateMonthNext = null;
-       var _dateMonthPrevious = null;
-       var _dateTime = null;
-       var _dateYear = null;
-       var _datePicker = null;
-       
-       var _callbackOpen = null;
-       var _callbackFocus = null;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Date/Picker
-        */
-       var DatePicker = {
-               /**
-                * Initializes all date and datetime input fields.
-                */
-               init: function() {
-                       this._setup();
-                       
-                       var elements = elBySelAll('input[type="date"]:not(.inputDatePicker), input[type="datetime"]:not(.inputDatePicker)');
-                       var now = new Date();
-                       for (var i = 0, length = elements.length; i < length; i++) {
-                               var element = elements[i];
-                               element.classList.add('inputDatePicker');
-                               element.readOnly = true;
-                               
-                               var isDateTime = (elAttr(element, 'type') === 'datetime');
-                               var isTimeOnly = (isDateTime && elDataBool(element, 'time-only'));
-                               var disableClear = elDataBool(element, 'disable-clear');
-                               var ignoreTimezone = isDateTime && elDataBool(element, 'ignore-timezone');
-                               var isBirthday = element.classList.contains('birthday');
-                               
-                               elData(element, 'is-date-time', isDateTime);
-                               elData(element, 'is-time-only', isTimeOnly);
-                               
-                               // convert value
-                               var date = null, value = elAttr(element, 'value');
-                               
-                               // ignore the timezone, if the value is only a date (YYYY-MM-DD)
-                               var isDateOnly = /^\d+-\d+-\d+$/.test(value);
-                               
-                               if (elAttr(element, 'value')) {
-                                       if (isTimeOnly) {
-                                               date = new Date();
-                                               var tmp = value.split(':');
-                                               date.setHours(tmp[0], tmp[1]);
-                                       }
-                                       else {
-                                               if (ignoreTimezone || isBirthday || isDateOnly) {
-                                                       var timezoneOffset = new Date(value).getTimezoneOffset();
-                                                       var timezone = (timezoneOffset > 0) ? '-' : '+'; // -120 equals GMT+0200
-                                                       timezoneOffset = Math.abs(timezoneOffset);
-                                                       var hours = (Math.floor(timezoneOffset / 60)).toString();
-                                                       var minutes = (timezoneOffset % 60).toString();
-                                                       timezone += (hours.length === 2) ? hours : '0' + hours;
-                                                       timezone += ':';
-                                                       timezone += (minutes.length === 2) ? minutes : '0' + minutes;
-                                                       
-                                                       if (isBirthday || isDateOnly) {
-                                                               value += 'T00:00:00' + timezone;
-                                                       }
-                                                       else {
-                                                               value = value.replace(/[+-][0-9]{2}:[0-9]{2}$/, timezone);
-                                                       }
-                                               }
-                                               
-                                               date = new Date(value);
-                                       }
-                                       
-                                       var time = date.getTime();
-                                       
-                                       // check for invalid dates
-                                       if (isNaN(time)) {
-                                               value = '';
-                                       }
-                                       else {
-                                               elData(element, 'value', time);
-                                               var format = (isTimeOnly) ? 'formatTime' : ('formatDate' + (isDateTime ? 'Time' : ''));
-                                               value = DateUtil[format](date);
-                                       }
-                               }
-                               
-                               var isEmpty = (value.length === 0);
-                               
-                               // handle birthday input
-                               if (isBirthday) {
-                                       elData(element, 'min-date', '120');
-                                       
-                                       // do not use 'now' here, all though it makes sense, it causes bad UX 
-                                       elData(element, 'max-date', new Date().getFullYear() + '-12-31');
-                               }
-                               else {
-                                       if (element.min) elData(element, 'min-date', element.min);
-                                       if (element.max) elData(element, 'max-date', element.max);
-                               }
-                               
-                               this._initDateRange(element, now, true);
-                               this._initDateRange(element, now, false);
-                               
-                               if (elData(element, 'min-date') === elData(element, 'max-date')) {
-                                       throw new Error("Minimum and maximum date cannot be the same (element id '" + element.id + "').");
-                               }
-                               
-                               // change type to prevent browser's datepicker to trigger
-                               element.type = 'text';
-                               element.value = value;
-                               elData(element, 'empty', isEmpty);
-                               
-                               if (elData(element, 'placeholder')) {
-                                       elAttr(element, 'placeholder', elData(element, 'placeholder'));
-                               }
-                               
-                               // add a hidden element to hold the actual date
-                               var shadowElement = elCreate('input');
-                               shadowElement.id = element.id + 'DatePicker';
-                               shadowElement.name = element.name;
-                               shadowElement.type = 'hidden';
-                               
-                               if (date !== null) {
-                                       if (isTimeOnly) {
-                                               shadowElement.value = DateUtil.format(date, 'H:i');
-                                       }
-                                       else if (ignoreTimezone) {
-                                               shadowElement.value = DateUtil.format(date, 'Y-m-dTH:i:s');
-                                       }
-                                       else {
-                                               shadowElement.value = DateUtil.format(date, (isDateTime) ? 'c' : 'Y-m-d');
-                                       }
-                               }
-                               
-                               element.parentNode.insertBefore(shadowElement, element);
-                               element.removeAttribute('name');
-                               
-                               element.addEventListener(WCF_CLICK_EVENT, _callbackOpen);
-                               
-                               if (!element.disabled) {
-                                       // create input addon
-                                       var container = elCreate('div');
-                                       container.className = 'inputAddon';
-                                       
-                                       var button = elCreate('a');
-                                       
-                                       button.className = 'inputSuffix button jsTooltip';
-                                       button.href = '#';
-                                       elAttr(button, 'role', 'button');
-                                       elAttr(button, 'tabindex', '0');
-                                       elAttr(button, 'title', Language.get('wcf.date.datePicker'));
-                                       elAttr(button, 'aria-label', Language.get('wcf.date.datePicker'));
-                                       elAttr(button, 'aria-haspopup', true);
-                                       elAttr(button, 'aria-expanded', false);
-                                       button.addEventListener(WCF_CLICK_EVENT, _callbackOpen);
-                                       container.appendChild(button);
-                                       
-                                       var icon = elCreate('span');
-                                       icon.className = 'icon icon16 fa-calendar';
-                                       button.appendChild(icon);
-                                       
-                                       element.parentNode.insertBefore(container, element);
-                                       container.insertBefore(element, button);
-                                       
-                                       if (!disableClear) {
-                                               button = elCreate('a');
-                                               button.className = 'inputSuffix button';
-                                               button.addEventListener(WCF_CLICK_EVENT, this.clear.bind(this, element));
-                                               if (isEmpty) button.style.setProperty('visibility', 'hidden', '');
-                                               
-                                               container.appendChild(button);
-                                               
-                                               icon = elCreate('span');
-                                               icon.className = 'icon icon16 fa-times';
-                                               button.appendChild(icon);
-                                       }
-                               }
-                               
-                               // check if the date input has one of the following classes set otherwise default to 'short'
-                               var hasClass = false, knownClasses = ['tiny', 'short', 'medium', 'long'];
-                               for (var j = 0; j < 4; j++) {
-                                       if (element.classList.contains(knownClasses[j])) {
-                                               hasClass = true;
-                                       }
-                               }
-                               
-                               if (!hasClass) {
-                                       element.classList.add('short');
-                               }
-                               
-                               _data.set(element, {
-                                       clearButton: button,
-                                       shadow: shadowElement,
-                                       
-                                       disableClear: disableClear,
-                                       isDateTime: isDateTime,
-                                       isEmpty: isEmpty,
-                                       isTimeOnly: isTimeOnly,
-                                       ignoreTimezone: ignoreTimezone,
-                                       
-                                       onClose: null
-                               });
-                       }
-               },
-               
-               /**
-                * Initializes the minimum/maximum date range.
-                * 
-                * @param       {Element}       element         input element
-                * @param       {Date}          now             current date
-                * @param       {boolean}       isMinDate       true for the minimum date
-                */
-               _initDateRange: function(element, now, isMinDate) {
-                       var attribute = 'data-' + (isMinDate ? 'min' : 'max') + '-date';
-                       var value = (element.hasAttribute(attribute)) ? elAttr(element, attribute).trim() : '';
-                       
-                       if (value.match(/^(\d{4})-(\d{2})-(\d{2})$/)) {
-                               // YYYY-mm-dd
-                               value = new Date(value).getTime();
-                       }
-                       else if (value === 'now') {
-                               value = now.getTime();
-                       }
-                       else if (value.match(/^\d{1,3}$/)) {
-                               // relative time span in years
-                               var date = new Date(now.getTime());
-                               date.setFullYear(date.getFullYear() + ~~value * (isMinDate ? -1 : 1));
-                               
-                               value = date.getTime();
-                       }
-                       else if (value.match(/^datePicker-(.+)$/)) {
-                               // element id, e.g. `datePicker-someOtherElement`
-                               value = RegExp.$1;
-                               
-                               if (elById(value) === null) {
-                                       throw new Error("Reference date picker identified by '" + value + "' does not exists (element id: '" + element.id + "').");
-                               }
-                       }
-                       else if (/^\d{4}\-\d{2}\-\d{2}T/.test(value)) {
-                               value = new Date(value).getTime();
-                       }
-                       else {
-                               value = new Date((isMinDate ? 1902 : 2038), 0, 1).getTime();
-                       }
-                       
-                       elAttr(element, attribute, value);
-               },
-               
-               /**
-                * Sets up callbacks and event listeners.
-                */
-               _setup: function() {
-                       if (_didInit) return;
-                       _didInit = true;
-                       
-                       _firstDayOfWeek = ~~Language.get('wcf.date.firstDayOfTheWeek');
-                       _callbackOpen = this._open.bind(this);
-                       
-                       DomChangeListener.add('WoltLabSuite/Core/Date/Picker', this.init.bind(this));
-                       UiCloseOverlay.add('WoltLabSuite/Core/Date/Picker', this._close.bind(this));
-               },
-               
-               /**
-                * Opens the date picker.
-                * 
-                * @param       {object}        event           event object
-                */
-               _open: function(event) {
-                       event.preventDefault();
-                       event.stopPropagation();
-                       
-                       this._createPicker();
-                       
-                       if (_callbackFocus === null) {
-                               _callbackFocus = this._maintainFocus.bind(this);
-                               document.body.addEventListener('focus', _callbackFocus, { capture: true });
-                       }
-                       
-                       var input = (event.currentTarget.nodeName === 'INPUT') ? event.currentTarget : event.currentTarget.previousElementSibling;
-                       if (input === _input) {
-                               this._close();
-                               return;
-                       }
-                       
-                       var dialogContent = DomTraverse.parentByClass(input, 'dialogContent');
-                       if (dialogContent !== null) {
-                               if (!elDataBool(dialogContent, 'has-datepicker-scroll-listener')) {
-                                       dialogContent.addEventListener('scroll', this._onDialogScroll.bind(this));
-                                       elData(dialogContent, 'has-datepicker-scroll-listener', 1);
-                               }
-                       }
-                       
-                       _input = input;
-                       var data = _data.get(_input), date, value = elData(_input, 'value');
-                       if (value) {
-                               date = new Date(+value);
-                               
-                               if (date.toString() === 'Invalid Date') {
-                                       date = new Date();
-                               }
-                       }
-                       else {
-                               date = new Date();
-                       }
-                       
-                       // set min/max date
-                       _minDate = elData(_input, 'min-date');
-                       if (_minDate.match(/^datePicker-(.+)$/)) _minDate = elData(elById(RegExp.$1), 'value');
-                       _minDate = new Date(+_minDate);
-                       
-                       _maxDate = elData(_input, 'max-date');
-                       if (_maxDate.match(/^datePicker-(.+)$/)) _maxDate = elData(elById(RegExp.$1), 'value');
-                       _maxDate = new Date(+_maxDate);
-                       
-                       if (data.isDateTime) {
-                               _dateHour.value = date.getHours();
-                               _dateMinute.value = date.getMinutes();
-                               
-                               _datePicker.classList.add('datePickerTime');
-                       }
-                       else {
-                               _datePicker.classList.remove('datePickerTime');
-                       }
-                       
-                       _datePicker.classList[(data.isTimeOnly) ? 'add' : 'remove']('datePickerTimeOnly');
-                       
-                       this._renderPicker(date.getDate(), date.getMonth(), date.getFullYear());
-                       
-                       UiAlignment.set(_datePicker, _input);
-                       
-                       elAttr(_input.nextElementSibling, 'aria-expanded', true);
-                       
-                       _wasInsidePicker = false;
-               },
-               
-               /**
-                * Closes the date picker.
-                */
-               _close: function() {
-                       if (_datePicker !== null && _datePicker.classList.contains('active')) {
-                               _datePicker.classList.remove('active');
-                               
-                               var data = _data.get(_input);
-                               if (typeof data.onClose === 'function') {
-                                       data.onClose();
-                               }
-                               
-                               EventHandler.fire('WoltLabSuite/Core/Date/Picker', 'close', {element: _input});
-                               
-                               elAttr(_input.nextElementSibling, 'aria-expanded', false);
-                               _input = null;
-                               _minDate = 0;
-                               _maxDate = 0;
-                       }
-               },
-               
-               /**
-                * Updates the position of the date picker in a dialog if the dialog content
-                * is scrolled.
-                * 
-                * @param       {Event}         event   scroll event
-                */
-               _onDialogScroll: function(event) {
-                       if (_input === null) {
-                               return;
-                       }
-                       
-                       var dialogContent = event.currentTarget;
-                       
-                       var offset = DomUtil.offset(_input);
-                       var dialogOffset = DomUtil.offset(dialogContent);
-                       
-                       // check if date picker input field is still (partially) visible
-                       if (offset.top + _input.clientHeight <= dialogOffset.top) {
-                               // top check
-                               this._close();
-                       }
-                       else if (offset.top >= dialogOffset.top + dialogContent.offsetHeight) {
-                               // bottom check
-                               this._close();
-                       }
-                       else if (offset.left <= dialogOffset.left) {
-                               // left check
-                               this._close();
-                       }
-                       else if (offset.left >= dialogOffset.left + dialogContent.offsetWidth) {
-                               // right check
-                               this._close();
-                       }
-                       else {
-                               UiAlignment.set(_datePicker, _input);
-                       }
-               },
-               
-               /**
-                * Renders the full picker on init.
-                * 
-                * @param       {int}           day
-                * @param       {int}           month
-                * @param       {int}           year
-                */
-               _renderPicker: function(day, month, year) {
-                       this._renderGrid(day, month, year);
-                       
-                       // create options for month and year
-                       var years = '';
-                       for (var i = _minDate.getFullYear(), last = _maxDate.getFullYear(); i <= last; i++) {
-                               years += '<option value="' + i + '">' + i + '</option>';
-                       }
-                       _dateYear.innerHTML = years;
-                       _dateYear.value = year;
-                       
-                       _dateMonth.value = month;
-                       
-                       _datePicker.classList.add('active');
-               },
-               
-               /**
-                * Updates the date grid.
-                * 
-                * @param       {int}           day
-                * @param       {int}           month
-                * @param       {int}           year
-                */
-               _renderGrid: function(day, month, year) {
-                       var cell, hasDay = (day !== undefined), hasMonth = (month !== undefined), i;
-                       
-                       day = ~~day || ~~elData(_dateGrid, 'day');
-                       month = ~~month;
-                       year = ~~year;
-                       
-                       // rebuild cells
-                       if (hasMonth || year) {
-                               var rebuildMonths = (year !== 0);
-                               
-                               // rebuild grid
-                               var fragment = document.createDocumentFragment();
-                               fragment.appendChild(_dateGrid);
-                               
-                               if (!hasMonth) month = ~~elData(_dateGrid, 'month');
-                               year = year || ~~elData(_dateGrid, 'year');
-                               
-                               // check if current selection exceeds min/max date
-                               var date = new Date(year + '-' + ('0' + (month + 1).toString()).slice(-2) + '-' + ('0' + day.toString()).slice(-2));
-                               if (date < _minDate) {
-                                       year = _minDate.getFullYear();
-                                       month = _minDate.getMonth();
-                                       day = _minDate.getDate();
-                                       
-                                       _dateMonth.value = month;
-                                       _dateYear.value = year;
-                                       
-                                       rebuildMonths = true;
-                               }
-                               else if (date > _maxDate) {
-                                       year = _maxDate.getFullYear();
-                                       month = _maxDate.getMonth();
-                                       day = _maxDate.getDate();
-                                       
-                                       _dateMonth.value = month;
-                                       _dateYear.value = year;
-                                       
-                                       rebuildMonths = true;
-                               }
-                               
-                               date = new Date(year + '-' + ('0' + (month + 1).toString()).slice(-2) + '-01');
-                               
-                               // shift until first displayed day equals first day of week
-                               while (date.getDay() !== _firstDayOfWeek) {
-                                       date.setDate(date.getDate() - 1);
-                               }
-                               
-                               // show the last row
-                               elShow(_dateCells[35].parentNode);
-                               
-                               var selectable;
-                               var comparableMinDate = new Date(_minDate.getFullYear(), _minDate.getMonth(), _minDate.getDate());
-                               for (i = 0; i < 42; i++) {
-                                       if (i === 35 && date.getMonth() !== month) {
-                                               // skip the last row if it only contains the next month
-                                               elHide(_dateCells[35].parentNode);
-                                               
-                                               break;
-                                       }
-                                       
-                                       cell = _dateCells[i];
-                                       
-                                       cell.textContent = date.getDate();
-                                       selectable = (date.getMonth() === month);
-                                       if (selectable) {
-                                               if (date < comparableMinDate) selectable = false;
-                                               else if (date > _maxDate) selectable = false;
-                                       }
-                                       
-                                       cell.classList[selectable ? 'remove' : 'add']('otherMonth');
-                                       if (selectable) {
-                                               cell.href = '#';
-                                               elAttr(cell, 'role', 'button');
-                                               elAttr(cell, 'tabindex', '0');
-                                               elAttr(cell, 'title', DateUtil.formatDate(date));
-                                               elAttr(cell, 'aria-label', DateUtil.formatDate(date));
-                                       }
-                                       
-                                       date.setDate(date.getDate() + 1);
-                               }
-                               
-                               elData(_dateGrid, 'month', month);
-                               elData(_dateGrid, 'year', year);
-                               
-                               _datePicker.insertBefore(fragment, _dateTime);
-                               
-                               if (!hasDay) {
-                                       // check if date is valid
-                                       date = new Date(year, month, day);
-                                       if (date.getDate() !== day) {
-                                               while (date.getMonth() !== month) {
-                                                       date.setDate(date.getDate() - 1);
-                                               }
-                                               
-                                               day = date.getDate();
-                                       }
-                               }
-                               
-                               if (rebuildMonths) {
-                                       for (i = 0; i < 12; i++) {
-                                               var currentMonth = _dateMonth.children[i];
-                                               
-                                               currentMonth.disabled = (year === _minDate.getFullYear() && currentMonth.value < _minDate.getMonth()) || (year === _maxDate.getFullYear() && currentMonth.value > _maxDate.getMonth());
-                                       }
-                                       
-                                       var nextMonth = new Date(year + '-' + ('0' + (month + 1).toString()).slice(-2) + '-01');
-                                       nextMonth.setMonth(nextMonth.getMonth() + 1);
-                                       
-                                       _dateMonthNext.classList[(nextMonth < _maxDate) ? 'add' : 'remove']('active');
-                                       
-                                       var previousMonth = new Date(year + '-' + ('0' + (month + 1).toString()).slice(-2) + '-01');
-                                       previousMonth.setDate(previousMonth.getDate() - 1);
-                                       
-                                       _dateMonthPrevious.classList[(previousMonth > _minDate) ? 'add' : 'remove']('active');
-                               }
-                       }
-                       
-                       // update active day
-                       if (day) {
-                               for (i = 0; i < 35; i++) {
-                                       cell = _dateCells[i];
-                                       
-                                       cell.classList[(!cell.classList.contains('otherMonth') && ~~cell.textContent === day) ? 'add' : 'remove']('active');
-                               }
-                               
-                               elData(_dateGrid, 'day', day);
-                       }
-                       
-                       this._formatValue();
-               },
-               
-               /**
-                * Sets the visible and shadow value
-                */
-               _formatValue: function() {
-                       var data = _data.get(_input), date;
-                       
-                       if (elData(_input, 'empty') === 'true') {
-                               return;
-                       }
-                       
-                       if (data.isDateTime) {
-                               date = new Date(
-                                       elData(_dateGrid, 'year'),
-                                       elData(_dateGrid, 'month'),
-                                       elData(_dateGrid, 'day'),
-                                       _dateHour.value,
-                                       _dateMinute.value
-                               );
-                       }
-                       else {
-                               date = new Date(
-                                       elData(_dateGrid, 'year'),
-                                       elData(_dateGrid, 'month'),
-                                       elData(_dateGrid, 'day')
-                               );
-                       }
-
-                       this.setDate(_input, date);
-               },
-               
-               /**
-                * Creates the date picker DOM.
-                */
-               _createPicker: function() {
-                       if (_datePicker !== null) {
-                               return;
-                       }
-                       
-                       _datePicker = elCreate('div');
-                       _datePicker.className = 'datePicker';
-                       _datePicker.addEventListener(WCF_CLICK_EVENT, function(event) { event.stopPropagation(); });
-                       
-                       var header = elCreate('header');
-                       _datePicker.appendChild(header);
-                       
-                       _dateMonthPrevious = elCreate('a');
-                       _dateMonthPrevious.className = 'previous jsTooltip';
-                       _dateMonthPrevious.href = '#';
-                       elAttr(_dateMonthPrevious, 'role', 'button');
-                       elAttr(_dateMonthPrevious, 'tabindex', '0');
-                       elAttr(_dateMonthPrevious, 'title', Language.get('wcf.date.datePicker.previousMonth'));
-                       elAttr(_dateMonthPrevious, 'aria-label', Language.get('wcf.date.datePicker.previousMonth'));
-                       _dateMonthPrevious.innerHTML = '<span class="icon icon16 fa-arrow-left"></span>';
-                       _dateMonthPrevious.addEventListener(WCF_CLICK_EVENT, this.previousMonth.bind(this));
-                       header.appendChild(_dateMonthPrevious);
-                       
-                       var monthYearContainer = elCreate('span');
-                       header.appendChild(monthYearContainer);
-                       
-                       _dateMonth = elCreate('select');
-                       _dateMonth.className = 'month jsTooltip';
-                       elAttr(_dateMonth, 'title', Language.get('wcf.date.datePicker.month'));
-                       elAttr(_dateMonth, 'aria-label', Language.get('wcf.date.datePicker.month'));
-                       _dateMonth.addEventListener('change', this._changeMonth.bind(this));
-                       monthYearContainer.appendChild(_dateMonth);
-                       
-                       var i, months = '', monthNames = Language.get('__monthsShort');
-                       for (i = 0; i < 12; i++) {
-                               months += '<option value="' + i + '">' + monthNames[i] + '</option>';
-                       }
-                       _dateMonth.innerHTML = months;
-                       
-                       _dateYear = elCreate('select');
-                       _dateYear.className = 'year jsTooltip';
-                       elAttr(_dateYear, 'title', Language.get('wcf.date.datePicker.year'));
-                       elAttr(_dateYear, 'aria-label', Language.get('wcf.date.datePicker.year'));
-                       _dateYear.addEventListener('change', this._changeYear.bind(this));
-                       monthYearContainer.appendChild(_dateYear);
-                       
-                       _dateMonthNext = elCreate('a');
-                       _dateMonthNext.className = 'next jsTooltip';
-                       _dateMonthNext.href = '#';
-                       elAttr(_dateMonthNext, 'role', 'button');
-                       elAttr(_dateMonthNext, 'tabindex', '0');
-                       elAttr(_dateMonthNext, 'title', Language.get('wcf.date.datePicker.nextMonth'));
-                       elAttr(_dateMonthNext, 'aria-label', Language.get('wcf.date.datePicker.nextMonth'));
-                       _dateMonthNext.innerHTML = '<span class="icon icon16 fa-arrow-right"></span>';
-                       _dateMonthNext.addEventListener(WCF_CLICK_EVENT, this.nextMonth.bind(this));
-                       header.appendChild(_dateMonthNext);
-                       
-                       _dateGrid = elCreate('ul');
-                       _datePicker.appendChild(_dateGrid);
-                       
-                       var item = elCreate('li');
-                       item.className = 'weekdays';
-                       _dateGrid.appendChild(item);
-                       
-                       var span, weekdays = Language.get('__daysShort');
-                       for (i = 0; i < 7; i++) {
-                               var day = i + _firstDayOfWeek;
-                               if (day > 6) day -= 7;
-                               
-                               span = elCreate('span');
-                               span.textContent = weekdays[day];
-                               item.appendChild(span);
-                       }
-                       
-                       // create date grid
-                       var callbackClick = this._click.bind(this), cell, row;
-                       for (i = 0; i < 6; i++) {
-                               row = elCreate('li');
-                               _dateGrid.appendChild(row);
-                               
-                               for (var j = 0; j < 7; j++) {
-                                       cell = elCreate('a');
-                                       cell.addEventListener(WCF_CLICK_EVENT, callbackClick);
-                                       _dateCells.push(cell);
-                                       
-                                       row.appendChild(cell);
-                               }
-                       }
-                       
-                       _dateTime = elCreate('footer');
-                       _datePicker.appendChild(_dateTime);
-                       
-                       _dateHour = elCreate('select');
-                       _dateHour.className = 'hour';
-                       elAttr(_dateHour, 'title', Language.get('wcf.date.datePicker.hour'));
-                       elAttr(_dateHour, 'aria-label', Language.get('wcf.date.datePicker.hour'));
-                       _dateHour.addEventListener('change', this._formatValue.bind(this));
-                       
-                       var tmp = '';
-                       var date = new Date(2000, 0, 1);
-                       var timeFormat = Language.get('wcf.date.timeFormat').replace(/:/, '').replace(/[isu]/g, '');
-                       for (i = 0; i < 24; i++) {
-                               date.setHours(i);
-                               tmp += '<option value="' + i + '">' + DateUtil.format(date, timeFormat) + "</option>";
-                       }
-                       _dateHour.innerHTML = tmp;
-                       
-                       _dateTime.appendChild(_dateHour);
-                       
-                       _dateTime.appendChild(document.createTextNode('\u00A0:\u00A0'));
-                       
-                       _dateMinute = elCreate('select');
-                       _dateMinute.className = 'minute';
-                       elAttr(_dateMinute, 'title', Language.get('wcf.date.datePicker.minute'));
-                       elAttr(_dateMinute, 'aria-label', Language.get('wcf.date.datePicker.minute'));
-                       _dateMinute.addEventListener('change', this._formatValue.bind(this));
-                       
-                       tmp = '';
-                       for (i = 0; i < 60; i++) {
-                               tmp += '<option value="' + i + '">' + (i < 10 ? '0' + i.toString() : i) + '</option>';
-                       }
-                       _dateMinute.innerHTML = tmp;
-                       
-                       _dateTime.appendChild(_dateMinute);
-                       
-                       document.body.appendChild(_datePicker);
-               },
-               
-               /**
-                * Shows the previous month.
-                */
-               previousMonth: function(event) {
-                       event.preventDefault();
-                       
-                       if (_dateMonth.value === '0') {
-                               _dateMonth.value = 11;
-                               _dateYear.value = ~~_dateYear.value - 1;
-                       }
-                       else {
-                               _dateMonth.value = ~~_dateMonth.value - 1;
-                       }
-                       
-                       this._renderGrid(undefined, _dateMonth.value, _dateYear.value);
-               },
-               
-               /**
-                * Shows the next month.
-                */
-               nextMonth: function(event) {
-                       event.preventDefault();
-                       
-                       if (_dateMonth.value === '11') {
-                               _dateMonth.value = 0;
-                               _dateYear.value = ~~_dateYear.value + 1;
-                       }
-                       else {
-                               _dateMonth.value = ~~_dateMonth.value + 1;
-                       }
-                       
-                       this._renderGrid(undefined, _dateMonth.value, _dateYear.value);
-               },
-               
-               /**
-                * Handles changes to the month select element.
-                * 
-                * @param       {object}        event           event object
-                */
-               _changeMonth: function(event) {
-                       this._renderGrid(undefined, event.currentTarget.value);
-               },
-               
-               /**
-                * Handles changes to the year select element.
-                * 
-                * @param       {object}        event           event object
-                */
-               _changeYear: function(event) {
-                       this._renderGrid(undefined, undefined, event.currentTarget.value);
-               },
-               
-               /**
-                * Handles clicks on an individual day.
-                * 
-                * @param       {object}        event           event object
-                */
-               _click: function(event) {
-                       event.preventDefault();
-                       
-                       if (event.currentTarget.classList.contains('otherMonth')) {
-                               return;
-                       }
-                       
-                       elData(_input, 'empty', false);
-                       
-                       this._renderGrid(event.currentTarget.textContent);
-                       
-                       var data = _data.get(_input);
-                       if (!data.isDateTime) {
-                               this._close();
-                       }
-               },
-               
-               /**
-                * Returns the current Date object or null.
-                * 
-                * @param       {(Element|string)}      element         input element or id
-                * @return      {?Date}                 Date object or null
-                */
-               getDate: function(element) {
-                       element = this._getElement(element);
-                       
-                       if (element.hasAttribute('data-value')) {
-                               return new Date(+elData(element, 'value'));
-                       }
-                       
-                       return null;
-               },
-               
-               /**
-                * Sets the date of given element.
-                * 
-                * @param       {(HTMLInputElement|string)}     element         input element or id
-                * @param       {Date}                          date            Date object
-                */
-               setDate: function(element, date) {
-                       element = this._getElement(element);
-                       var data = _data.get(element);
-                       
-                       elData(element, 'value', date.getTime());
-
-                       var format = '', value;
-                       if (data.isDateTime) {
-                               if (data.isTimeOnly) {
-                                       value = DateUtil.formatTime(date);
-                                       format = 'H:i';
-                               }
-                               else if (data.ignoreTimezone) {
-                                       value = DateUtil.formatDateTime(date);
-                                       format = 'Y-m-dTH:i:s';
-                               }
-                               else {
-                                       value = DateUtil.formatDateTime(date);
-                                       format = 'c';
-                               }
-                       }
-                       else {
-                               value = DateUtil.formatDate(date);
-                               format = 'Y-m-d';
-                       }
-
-                       element.value = value;
-                       data.shadow.value = DateUtil.format(date, format);
-
-                       // show clear button
-                       if (!data.disableClear) {
-                               data.clearButton.style.removeProperty('visibility');
-                       }
-               },
-               
-               /**
-                * Returns the current value.
-                * 
-                * @param       {(Element|string)}      element         input element or id
-                * @return      {string}                current date value
-                */
-               getValue: function (element) {
-                       element = this._getElement(element);
-                       var data = _data.get(element);
-                       
-                       if (data) {
-                               return data.shadow.value;
-                       }
-                       
-                       return '';
-               },
-               
-               /**
-                * Clears the date value of given element.
-                * 
-                * @param       {(HTMLInputElement|string)}     element         input element or id
-                */
-               clear: function(element) {
-                       element = this._getElement(element);
-                       var data = _data.get(element);
-                       
-                       element.removeAttribute('data-value');
-                       element.value = '';
-                       
-                       if (!data.disableClear) data.clearButton.style.setProperty('visibility', 'hidden', '');
-                       data.isEmpty = true;
-                       data.shadow.value = '';
-               },
-               
-               /**
-                * Reverts the date picker into a normal input field.
-                * 
-                * @param       {(HTMLInputElement|string)}     element         input element or id
-                */
-               destroy: function(element) {
-                       element = this._getElement(element);
-                       var data = _data.get(element);
-                       
-                       var container = element.parentNode;
-                       container.parentNode.insertBefore(element, container);
-                       elRemove(container);
-                       
-                       elAttr(element, 'type', 'date' + (data.isDateTime ? 'time' : ''));
-                       element.name = data.shadow.name;
-                       element.value = data.shadow.value;
-                       
-                       element.removeAttribute('data-value');
-                       element.removeEventListener(WCF_CLICK_EVENT, _callbackOpen);
-                       elRemove(data.shadow);
-                       
-                       element.classList.remove('inputDatePicker');
-                       element.readOnly = false;
-                       _data['delete'](element);
-               },
-               
-               /**
-                * Sets the callback invoked on picker close.
-                * 
-                * @param       {(Element|string)}      element         input element or id
-                * @param       {function}              callback        callback function
-                */
-               setCloseCallback: function(element, callback) {
-                       element = this._getElement(element);
-                       _data.get(element).onClose = callback;
-               },
-               
-               /**
-                * Validates given element or id if it represents an active date picker.
-                * 
-                * @param       {(Element|string)}      element         input element or id
-                * @return      {Element}               input element
-                */
-               _getElement: function(element) {
-                       if (typeof element === 'string') element = elById(element);
-                       
-                       if (!(element instanceof Element) || !element.classList.contains('inputDatePicker') || !_data.has(element)) {
-                               throw new Error("Expected a valid date picker input element or id.");
-                       }
-                       
-                       return element;
-               },
-               
-               /**
-                * @param {Event} event
-                */
-               _maintainFocus: function(event) {
-                       if (_datePicker !== null && _datePicker.classList.contains('active')) {
-                               if (!_datePicker.contains(event.target)) {
-                                       if (_wasInsidePicker) {
-                                               _input.nextElementSibling.focus();
-                                               _wasInsidePicker = false;
-                                       }
-                                       else {
-                                               elBySel('.previous', _datePicker).focus();
-                                       }
-                               }
-                               else {
-                                       _wasInsidePicker = true;
-                               }
-                       }
-               }
-       };
-       
-       // backward-compatibility for `$.ui.datepicker` shim
-       window.__wcf_bc_datePicker = DatePicker;
-       
-       return DatePicker;
-});
-
-/**
- * Provides page actions such as "jump to top" and clipboard actions.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Page/Action
- */
-define('WoltLabSuite/Core/Ui/Page/Action',['Dictionary', 'Dom/Util'], function(Dictionary, DomUtil) {
-       "use strict";
-       
-       var _buttons = new Dictionary();
-       var _container = null;
-       var _didInit = false;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Page/Action
-        */
-       return {
-               /**
-                * Initializes the page action container.
-                */
-               setup: function() {
-                       _didInit = true;
-                       
-                       _container = elCreate('ul');
-                       _container.className = 'pageAction';
-                       document.body.appendChild(_container);
-               },
-               
-               /**
-                * Adds a button to the page action list. You can optionally provide a button name to
-                * insert the button right before it. Unmatched button names or empty value will cause
-                * the button to be prepended to the list.
-                * 
-                * @param       {string}        buttonName              unique identifier
-                * @param       {Element}       button                  button element, must not be wrapped in a <li>
-                * @param       {string=}       insertBeforeButton      insert button before element identified by provided button name
-                */
-               add: function(buttonName, button, insertBeforeButton) {
-                       if (_didInit === false) this.setup();
-                       
-                       var listItem = elCreate('li');
-                       button.classList.add('button');
-                       button.classList.add('buttonPrimary');
-                       listItem.appendChild(button);
-                       elAttr(listItem, 'aria-hidden', (buttonName === 'toTop' ? 'true' : 'false'));
-                       elData(listItem, 'name', buttonName);
-                       
-                       // force 'to top' button to be always at the most outer position
-                       if (buttonName === 'toTop') {
-                               listItem.className = 'toTop initiallyHidden';
-                               _container.appendChild(listItem);
-                       }
-                       else {
-                               var insertBefore = null;
-                               if (insertBeforeButton) {
-                                       insertBefore = _buttons.get(insertBeforeButton);
-                                       if (insertBefore !== undefined) {
-                                               insertBefore = insertBefore.parentNode;
-                                       }
-                               }
-                               
-                               if (insertBefore === null && _container.childElementCount) {
-                                       insertBefore = _container.children[0];
-                               }
-                               
-                               if (insertBefore === null) {
-                                       DomUtil.prepend(listItem, _container);
-                               }
-                               else {
-                                       _container.insertBefore(listItem, insertBefore);
-                               }
-                       }
-                       
-                       _buttons.set(buttonName, button);
-                       this._renderContainer();
-               },
-               
-               /**
-                * Returns true if there is a registered button with the provided name.
-                * 
-                * @param       {string}        buttonName      unique identifier
-                * @return      {boolean}       true if there is a registered button with this name
-                */
-               has: function (buttonName) {
-                       return _buttons.has(buttonName);
-               },
-               
-               /**
-                * Returns the stored button by name or undefined.
-                * 
-                * @param       {string}        buttonName      unique identifier
-                * @return      {Element}       button element or undefined
-                */
-               get: function(buttonName) {
-                       return _buttons.get(buttonName);
-               },
-               
-               /**
-                * Removes a button by its button name.
-                * 
-                * @param       {string}        buttonName      unique identifier
-                */
-               remove: function(buttonName) {
-                       var button = _buttons.get(buttonName);
-                       if (button !== undefined) {
-                               var listItem = button.parentNode;
-                               listItem.addEventListener('animationend', function () {
-                                       try {
-                                               _container.removeChild(listItem);
-                                               _buttons.delete(buttonName);
-                                       }
-                                       catch (e) {
-                                               // ignore errors if the element has already been removed
-                                       }
-                               });
-                               
-                               this.hide(buttonName);
-                       }
-               },
-               
-               /**
-                * Hides a button by its button name.
-                * 
-                * @param       {string}        buttonName      unique identifier
-                */
-               hide: function(buttonName) {
-                       var button = _buttons.get(buttonName);
-                       if (button) {
-                               elAttr(button.parentNode, 'aria-hidden', 'true');
-                               this._renderContainer();
-                       }
-               },
-               
-               /**
-                * Shows a button by its button name.
-                * 
-                * @param       {string}        buttonName      unique identifier
-                */
-               show: function(buttonName) {
-                       var button = _buttons.get(buttonName);
-                       if (button) {
-                               if (button.parentNode.classList.contains('initiallyHidden')) {
-                                       button.parentNode.classList.remove('initiallyHidden');
-                               }
-                               
-                               elAttr(button.parentNode, 'aria-hidden', 'false');
-                               this._renderContainer();
-                       }
-               },
-               
-               /**
-                * Toggles the container's visibility.
-                * 
-                * @protected
-                */
-               _renderContainer: function() {
-                       var hasVisibleItems = false;
-                       if (_container.childElementCount) {
-                               for (var i = 0, length = _container.childElementCount; i < length; i++) {
-                                       if (elAttr(_container.children[i], 'aria-hidden') === 'false') {
-                                               hasVisibleItems = true;
-                                               break;
-                                       }
-                               }
-                       }
-                       
-                       _container.classList[(hasVisibleItems ? 'add' : 'remove')]('active');
-               }
-       };
-});
-
-/**
- * Provides a link to scroll to top once the page is scrolled by at least 50% the height of the window.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Page/JumpToTop
- */
-define('WoltLabSuite/Core/Ui/Page/JumpToTop',['Environment', 'Language', './Action'], function(Environment, Language, PageAction) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function JumpToTop() { this.init(); }
-       JumpToTop.prototype = {
-               /**
-                * Initializes the top link for desktop browsers only.
-                */
-               init: function() {
-                       // top link is not available on smartphones and tablets (they have a built-in function to accomplish this)
-                       if (Environment.platform() !== 'desktop') {
-                               return;
-                       }
-                       
-                       this._callbackScrollEnd = this._afterScroll.bind(this);
-                       this._timeoutScroll = null;
-                       
-                       var button = elCreate('a');
-                       button.className = 'jsTooltip';
-                       button.href = '#';
-                       elAttr(button, 'title', Language.get('wcf.global.scrollUp'));
-                       elAttr(button, 'role', 'button');
-                       button.innerHTML = '<span class="icon icon32 fa-angle-up"></span>';
-                       
-                       button.addEventListener(WCF_CLICK_EVENT, this._jump.bind(this));
-                       
-                       PageAction.add('toTop', button);
-                       
-                       window.addEventListener('scroll', this._scroll.bind(this));
-                       
-                       // invoke callback on page load
-                       this._afterScroll();
-               },
-               
-               /**
-                * Handles clicks on the top link.
-                * 
-                * @param       {Event}         event   event object
-                * @protected
-                */
-               _jump: function(event) {
-                       event.preventDefault();
-                       
-                       elById('top').scrollIntoView({ behavior: 'smooth' });
-               },
-               
-               /**
-                * Callback executed whenever the window is being scrolled.
-                * 
-                * @protected
-                */
-               _scroll: function() {
-                       if (this._timeoutScroll !== null) {
-                               window.clearTimeout(this._timeoutScroll);
-                       }
-                       
-                       this._timeoutScroll = window.setTimeout(this._callbackScrollEnd, 100);
-               },
-               
-               /**
-                * Delayed callback executed once the page has not been scrolled for a certain amount of time.
-                * 
-                * @protected
-                */
-               _afterScroll: function() {
-                       this._timeoutScroll = null;
-                       
-                       PageAction[(window.pageYOffset >= 300) ? 'show' : 'hide']('toTop');
-               }
-       };
-       
-       return JumpToTop;
-});
-
-/**
- * Bootstraps WCF's JavaScript.
- * It defines globals needed for backwards compatibility
- * and runs modules that are needed on page load.
- * 
- * @author     Tim Duesterhus
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Bootstrap
- */
-define(
-       'WoltLabSuite/Core/Bootstrap',[
-               'favico',                  'enquire',                'perfect-scrollbar',      'WoltLabSuite/Core/Date/Time/Relative',
-               'Ui/SimpleDropdown',       'WoltLabSuite/Core/Ui/Mobile',  'WoltLabSuite/Core/Ui/TabMenu', 'WoltLabSuite/Core/Ui/FlexibleMenu',
-               'Ui/Dialog',               'WoltLabSuite/Core/Ui/Tooltip', 'WoltLabSuite/Core/Language',   'WoltLabSuite/Core/Environment',
-               'WoltLabSuite/Core/Date/Picker', 'EventHandler',           'Core',                   'WoltLabSuite/Core/Ui/Page/JumpToTop',
-               'Devtools', 'Dom/ChangeListener'
-       ], 
-       function(
-                favico,                   enquire,                  perfectScrollbar,         DateTimeRelative,
-                UiSimpleDropdown,         UiMobile,                 UiTabMenu,                UiFlexibleMenu,
-                UiDialog,                 UiTooltip,                Language,                 Environment,
-                DatePicker,               EventHandler,             Core,                     UiPageJumpToTop,
-                Devtools, DomChangeListener
-       )
-{
-       "use strict";
-       
-       // perfectScrollbar does not need to be bound anywhere, it just has to be loaded for WCF.js
-       window.Favico = favico;
-       window.enquire = enquire;
-       // non strict equals by intent
-       if (window.WCF == null) window.WCF = { };
-       if (window.WCF.Language == null) window.WCF.Language = { };
-       window.WCF.Language.get = Language.get;
-       window.WCF.Language.add = Language.add;
-       window.WCF.Language.addObject = Language.addObject;
-       
-       // WCF.System.Event compatibility
-       window.__wcf_bc_eventHandler = EventHandler;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Bootstrap
-        */
-       return {
-               /**
-                * Initializes the core UI modifications and unblocks jQuery's ready event.
-                * 
-                * @param       {Object=}       options         initialization options
-                */
-               setup: function(options) {
-                       options = Core.extend({
-                               enableMobileMenu: true
-                       }, options);
-                       
-                       //noinspection JSUnresolvedVariable
-                       if (window.ENABLE_DEVELOPER_TOOLS) Devtools._internal_.enable();
-                       
-                       Environment.setup();
-                       
-                       DateTimeRelative.setup();
-                       DatePicker.init();
-                       
-                       UiSimpleDropdown.setup();
-                       UiMobile.setup({
-                               enableMobileMenu: options.enableMobileMenu
-                       });
-                       UiTabMenu.setup();
-                       //UiFlexibleMenu.setup();
-                       UiDialog.setup();
-                       UiTooltip.setup();
-                       
-                       // convert method=get into method=post
-                       var forms = elBySelAll('form[method=get]');
-                       for (var i = 0, length = forms.length; i < length; i++) {
-                               forms[i].setAttribute('method', 'post');
-                       }
-                       
-                       if (Environment.browser() === 'microsoft') {
-                               window.onbeforeunload = function() {
-                                       /* Prevent "Back navigation caching" (http://msdn.microsoft.com/en-us/library/ie/dn265017%28v=vs.85%29.aspx) */
-                               };
-                       }
-                       
-                       var interval = 0;
-                       interval = window.setInterval(function() {
-                               if (typeof window.jQuery === 'function') {
-                                       window.clearInterval(interval);
-                                       
-                                       // the 'jump to top' button triggers style recalculation/layout,
-                                       // putting it at the end of the jQuery queue avoids trashing the
-                                       // layout too early and thus delaying the page initialization
-                                       window.jQuery(function() {
-                                               new UiPageJumpToTop();
-                                       });
-                                       
-                                       window.jQuery.holdReady(false);
-                               }
-                       }, 20);
-                       
-                       this._initA11y();
-                       DomChangeListener.add('WoltLabSuite/Core/Bootstrap', this._initA11y.bind(this));
-               },
-               
-               _initA11y: function() {
-                       elBySelAll('nav:not([aria-label]):not([aria-labelledby]):not([role])', undefined, function(element) {
-                               elAttr(element, 'role', 'presentation');
-                       });
-                       
-                       elBySelAll('article:not([aria-label]):not([aria-labelledby]):not([role])', undefined, function(element) {
-                               elAttr(element, 'role', 'presentation');
-                       });
-               }
-       };
-});
-
-/**
- * Dialog based style changer.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Controller/Style/Changer
- */
-define('WoltLabSuite/Core/Controller/Style/Changer',['Ajax', 'Language', 'Ui/Dialog'], function(Ajax, Language, UiDialog) {
-       "use strict";
-       
-       /**
-        * @exports     WoltLabSuite/Core/Controller/Style/Changer
-        */
-       return {
-               /**
-                * Adds the style changer to the bottom navigation.
-                */
-               setup: function() {
-                       var link = elBySel('.jsButtonStyleChanger');
-                       if (link) {
-                               link.addEventListener(WCF_CLICK_EVENT, this.showDialog.bind(this));
-                       }
-               },
-               
-               /**
-                * Loads and displays the style change dialog.
-                * 
-                * @param       {object}        event   event object
-                */
-               showDialog: function(event) {
-                       event.preventDefault();
-                       
-                       UiDialog.open(this);
-               },
-               
-               _dialogSetup: function() {
-                       return {
-                               id: 'styleChanger',
-                               options: {
-                                       disableContentPadding: true,
-                                       title: Language.get('wcf.style.changeStyle')
-                               },
-                               source: {
-                                       data: {
-                                               actionName: 'getStyleChooser',
-                                               className: 'wcf\\data\\style\\StyleAction'
-                                       },
-                                       after: (function(content) {
-                                               var styles = elBySelAll('.styleList > li', content);
-                                               for (var i = 0, length = styles.length; i < length; i++) {
-                                                       var style = styles[i];
-                                                       
-                                                       style.classList.add('pointer');
-                                                       style.addEventListener(WCF_CLICK_EVENT, this._click.bind(this));
-                                               }
-                                       }).bind(this)
-                               }
-                       };
-               },
-               
-               /**
-                * Changes the style and reloads current page.
-                * 
-                * @param       {object}        event   event object
-                */
-               _click: function(event) {
-                       event.preventDefault();
-                       
-                       Ajax.apiOnce({
-                               data: {
-                                       actionName: 'changeStyle',
-                                       className: 'wcf\\data\\style\\StyleAction',
-                                       objectIDs: [ elData(event.currentTarget, 'style-id') ]
-                               },
-                               success: function() { window.location.reload(); }
-                       });
-               }
-       };
-});
-
-/**
- * Versatile popover manager.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Controller/Popover
- */
-define('WoltLabSuite/Core/Controller/Popover',['Ajax', 'Dictionary', 'Environment', 'Dom/ChangeListener', 'Dom/Util', 'Ui/Alignment'], function(Ajax, Dictionary, Environment, DomChangeListener, DomUtil, UiAlignment) {
-       "use strict";
-       
-       var _activeId = null;
-       var _cache = new Dictionary();
-       var _elements = new Dictionary();
-       var _handlers = new Dictionary();
-       var _hoverId = null;
-       var _suspended = false;
-       var _timeoutEnter = null;
-       var _timeoutLeave = null;
-       
-       var _popover = null;
-       var _popoverContent = null;
-       
-       var _callbackClick = null;
-       var _callbackHide = null;
-       var _callbackMouseEnter = null;
-       var _callbackMouseLeave = null;
-       
-       /** @const */ var STATE_NONE = 0;
-       /** @const */ var STATE_LOADING = 1;
-       /** @const */ var STATE_READY = 2;
-       
-       /** @const */ var DELAY_HIDE = 500;
-       /** @const */ var DELAY_SHOW = 800;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Controller/Popover
-        */
-       return {
-               /**
-                * Builds popover DOM elements and binds event listeners.
-                */
-               _setup: function() {
-                       if (_popover !== null) {
-                               return;
-                       }
-                       
-                       _popover = elCreate('div');
-                       _popover.className = 'popover forceHide';
-                       
-                       _popoverContent = elCreate('div');
-                       _popoverContent.className = 'popoverContent';
-                       _popover.appendChild(_popoverContent);
-                       
-                       var pointer = elCreate('span');
-                       pointer.className = 'elementPointer';
-                       pointer.appendChild(elCreate('span'));
-                       _popover.appendChild(pointer);
-                       
-                       document.body.appendChild(_popover);
-                       
-                       // static binding for callbacks (they don't change anyway and binding each time is expensive)
-                       _callbackClick = this._hide.bind(this);
-                       _callbackMouseEnter = this._mouseEnter.bind(this);
-                       _callbackMouseLeave = this._mouseLeave.bind(this);
-                       
-                       // event listener
-                       _popover.addEventListener('mouseenter', this._popoverMouseEnter.bind(this));
-                       _popover.addEventListener('mouseleave', _callbackMouseLeave);
-                       
-                       _popover.addEventListener('animationend', this._clearContent.bind(this));
-                       
-                       window.addEventListener('beforeunload', (function() {
-                               _suspended = true;
-                               
-                               if (_timeoutEnter !== null) {
-                                       window.clearTimeout(_timeoutEnter);
-                               }
-                               
-                               this._hide(true);
-                       }).bind(this));
-                       
-                       DomChangeListener.add('WoltLabSuite/Core/Controller/Popover', this._init.bind(this));
-               },
-               
-               /**
-                * Initializes a popover handler.
-                * 
-                * Usage:
-                * 
-                * ControllerPopover.init({
-                *      attributeName: 'data-object-id',
-                *      className: 'fooLink',
-                *      identifier: 'com.example.bar.foo',
-                *      loadCallback: function(objectId, popover) {
-                *              // request data for object id (e.g. via WoltLabSuite/Core/Ajax)
-                *              
-                *              // then call this to set the content
-                *              popover.setContent('com.example.bar.foo', objectId, htmlTemplateString);
-                *      }
-                * });
-                * 
-                * @param       {Object}        options         handler options
-                */
-               init: function(options) {
-                       if (Environment.platform() !== 'desktop') {
-                               return;
-                       }
-                       
-                       options.attributeName = options.attributeName || 'data-object-id';
-                       options.legacy = (options.legacy === true);
-                       
-                       this._setup();
-                       
-                       if (_handlers.has(options.identifier)) {
-                               return;
-                       }
-                       
-                       _handlers.set(options.identifier, {
-                               attributeName: options.attributeName,
-                               elements: options.legacy ? options.className : elByClass(options.className),
-                               legacy: options.legacy,
-                               loadCallback: options.loadCallback
-                       });
-                       
-                       this._init(options.identifier);
-               },
-               
-               /**
-                * Initializes a popover handler.
-                * 
-                * @param       {string}        identifier      handler identifier
-                */
-               _init: function(identifier) {
-                       if (typeof identifier === 'string' && identifier.length) {
-                               this._initElements(_handlers.get(identifier), identifier);
-                       }
-                       else {
-                               _handlers.forEach(this._initElements.bind(this));
-                       }
-               },
-               
-               /**
-                * Binds event listeners for popover-enabled elements.
-                * 
-                * @param       {Object}        options         handler options
-                * @param       {string}        identifier      handler identifier
-                */
-               _initElements: function(options, identifier) {
-                       var elements = options.legacy ? elBySelAll(options.elements) : options.elements;
-                       for (var i = 0, length = elements.length; i < length; i++) {
-                               var element = elements[i];
-                               
-                               var id = DomUtil.identify(element);
-                               if (_cache.has(id)) {
-                                       return;
-                               }
-                               // skip if element is in a popover
-                               if (element.closest('.popover') !== null) {
-                                       _cache.set(id, {
-                                               content: null,
-                                               state: STATE_NONE
-                                       });
-                                       return;
-                               }
-                               
-                               var objectId = (options.legacy) ? id : ~~element.getAttribute(options.attributeName);
-                               if (objectId === 0) {
-                                       continue;
-                               }
-                               
-                               element.addEventListener('mouseenter', _callbackMouseEnter);
-                               element.addEventListener('mouseleave', _callbackMouseLeave);
-                               
-                               if (element.nodeName === 'A' && elAttr(element, 'href')) {
-                                       element.addEventListener(WCF_CLICK_EVENT, _callbackClick);
-                               }
-                               
-                               var cacheId = identifier + "-" + objectId;
-                               elData(element, 'cache-id', cacheId);
-                               
-                               _elements.set(id, {
-                                       element: element,
-                                       identifier: identifier,
-                                       objectId: objectId
-                               });
-                               
-                               if (!_cache.has(cacheId)) {
-                                       _cache.set(identifier + "-" + objectId, {
-                                               content: null,
-                                               state: STATE_NONE
-                                       });
-                               }
-                       }
-               },
-               
-               /**
-                * Sets the content for given identifier and object id.
-                * 
-                * @param       {string}        identifier      handler identifier
-                * @param       {int}           objectId        object id
-                * @param       {string}        content         HTML string
-                */
-               setContent: function(identifier, objectId, content) {
-                       var cacheId = identifier + "-" + objectId;
-                       var data = _cache.get(cacheId);
-                       if (data === undefined) {
-                               throw new Error("Unable to find element for object id '" + objectId + "' (identifier: '" + identifier + "').");
-                       }
-                       
-                       var fragment = DomUtil.createFragmentFromHtml(content);
-                       if (!fragment.childElementCount) fragment = DomUtil.createFragmentFromHtml('<p>' + content + '</p>');
-                       data.content = fragment;
-                       data.state = STATE_READY;
-                       
-                       if (_activeId) {
-                               var activeElement = _elements.get(_activeId).element;
-                               
-                               if (elData(activeElement, 'cache-id') === cacheId) {
-                                       this._show();
-                               }
-                       }
-               },
-               
-               /**
-                * Handles the mouse start hovering the popover-enabled element.
-                * 
-                * @param       {object}        event   event object
-                */
-               _mouseEnter: function(event) {
-                       if (_suspended) {
-                               return;
-                       }
-                       
-                       if (_timeoutEnter !== null) {
-                               window.clearTimeout(_timeoutEnter);
-                               _timeoutEnter = null;
-                       }
-                       
-                       var id = DomUtil.identify(event.currentTarget);
-                       if (_activeId === id && _timeoutLeave !== null) {
-                               window.clearTimeout(_timeoutLeave);
-                               _timeoutLeave = null;
-                       }
-                       
-                       _hoverId = id;
-                       
-                       _timeoutEnter = window.setTimeout((function() {
-                               _timeoutEnter = null;
-                               
-                               if (_hoverId === id) {
-                                       this._show();
-                               }
-                       }).bind(this), DELAY_SHOW);
-               },
-               
-               /**
-                * Handles the mouse leaving the popover-enabled element or the popover itself.
-                */
-               _mouseLeave: function() {
-                       _hoverId = null;
-                       
-                       if (_timeoutLeave !== null) {
-                               return;
-                       }
-                       
-                       if (_callbackHide === null) {
-                               _callbackHide = this._hide.bind(this);
-                       }
-                       
-                       if (_timeoutLeave !== null) {
-                               window.clearTimeout(_timeoutLeave);
-                       }
-                       
-                       _timeoutLeave = window.setTimeout(_callbackHide, DELAY_HIDE);
-               },
-               
-               /**
-                * Handles the mouse start hovering the popover element.
-                */
-               _popoverMouseEnter: function() {
-                       if (_timeoutLeave !== null) {
-                               window.clearTimeout(_timeoutLeave);
-                               _timeoutLeave = null;
-                       }
-               },
-               
-               /**
-                * Shows the popover and loads content on-the-fly.
-                */
-               _show: function() {
-                       if (_timeoutLeave !== null) {
-                               window.clearTimeout(_timeoutLeave);
-                               _timeoutLeave = null;
-                       }
-                       
-                       var forceHide = false;
-                       if (_popover.classList.contains('active')) {
-                               if (_activeId !== _hoverId) {
-                                       this._hide();
-                                       
-                                       forceHide = true;
-                               }
-                       }
-                       else if (_popoverContent.childElementCount) {
-                               forceHide = true;
-                       }
-                       
-                       if (forceHide) {
-                               _popover.classList.add('forceHide');
-                               
-                               // force layout
-                               //noinspection BadExpressionStatementJS
-                               _popover.offsetTop;
-                               
-                               this._clearContent();
-                               
-                               _popover.classList.remove('forceHide');
-                       }
-                       
-                       _activeId = _hoverId;
-                       
-                       var elementData = _elements.get(_activeId);
-                       // check if source element is already gone
-                       if (elementData === undefined) {
-                               return;
-                       }
-                       
-                       var data = _cache.get(elData(elementData.element, 'cache-id'));
-                       
-                       if (data.state === STATE_READY) {
-                               _popoverContent.appendChild(data.content);
-                               
-                               this._rebuild(_activeId);
-                       }
-                       else if (data.state === STATE_NONE) {
-                               data.state = STATE_LOADING;
-                               
-                               _handlers.get(elementData.identifier).loadCallback(elementData.objectId, this);
-                       }
-               },
-               
-               /**
-                * Hides the popover element.
-                */
-               _hide: function() {
-                       if (_timeoutLeave !== null) {
-                               window.clearTimeout(_timeoutLeave);
-                               _timeoutLeave = null;
-                       }
-                       
-                       _popover.classList.remove('active');
-               },
-               
-               /**
-                * Clears popover content by moving it back into the cache.
-                */
-               _clearContent: function() {
-                       if (_activeId && _popoverContent.childElementCount && !_popover.classList.contains('active')) {
-                               var activeElData = _cache.get(elData(_elements.get(_activeId).element, 'cache-id'));
-                               while (_popoverContent.childNodes.length) {
-                                       activeElData.content.appendChild(_popoverContent.childNodes[0]);
-                               }
-                       }
-               },
-               
-               /**
-                * Rebuilds the popover.
-                */
-               _rebuild: function() {
-                       if (_popover.classList.contains('active')) {
-                               return;
-                       }
-                       
-                       _popover.classList.remove('forceHide');
-                       _popover.classList.add('active');
-                       
-                       UiAlignment.set(_popover, _elements.get(_activeId).element, {
-                               pointer: true,
-                               vertical: 'top'
-                       });
-               },
-               
-               _ajaxSetup: function() {
-                       return {
-                               silent: true
-                       };
-               },
-               
-               /**
-                * Sends an AJAX requests to the server, simple wrapper to reuse the request object.
-                * 
-                * @param       {Object}        data            request data
-                * @param       {function}      success         success callback
-                * @param       {function=}     failure         error callback
-                */
-               ajaxApi: function(data, success, failure) {
-                       if (typeof success !== 'function') {
-                               throw new TypeError("Expected a valid callback for parameter 'success'.");
-                       }
-                       
-                       Ajax.api(this, data, success, failure);
-               }
-       };
-});
-
-/**
- * Provides global helper methods to interact with ignored content.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/User/Ignore
- */
-define('WoltLabSuite/Core/Ui/User/Ignore',['List', 'Dom/ChangeListener'], function(List, DomChangeListener) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _rebuild: function() {},
-                       _removeClass: function() {}
-               };
-               return Fake;
-       }
-       
-       var _availableMessages = elByClass('ignoredUserMessage');
-       var _callback = null;
-       var _knownMessages = new List();
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/User/Ignore
-        */
-       return {
-               /**
-                * Initializes the click handler for each ignored message and listens for
-                * newly inserted messages.
-                */
-               init: function () {
-                       _callback = this._removeClass.bind(this);
-                       
-                       this._rebuild();
-                       
-                       DomChangeListener.add('WoltLabSuite/Core/Ui/User/Ignore', this._rebuild.bind(this));
-               },
-               
-               /**
-                * Adds ignored messages to the collection.
-                * 
-                * @protected
-                */
-               _rebuild: function() {
-                       var message;
-                       for (var i = 0, length = _availableMessages.length; i < length; i++) {
-                               message = _availableMessages[i];
-                               
-                               if (!_knownMessages.has(message)) {
-                                       message.addEventListener(WCF_CLICK_EVENT, _callback);
-                                       
-                                       _knownMessages.add(message);
-                               }
-                       }
-               },
-               
-               /**
-                * Reveals a message on click/tap and disables the listener.
-                * 
-                * @param       {Event}         event   event object
-                * @protected
-                */
-               _removeClass: function(event) {
-                       event.preventDefault();
-                       
-                       var message = event.currentTarget;
-                       message.classList.remove('ignoredUserMessage');
-                       message.removeEventListener(WCF_CLICK_EVENT, _callback);
-                       _knownMessages.delete(message);
-                       
-                       // Firefox selects the entire message on click for no reason
-                       window.getSelection().removeAllRanges();
-               }
-       };
-});
-
-/**
- * Handles main menu overflow and a11y.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Page/Header/Menu
- */
-define('WoltLabSuite/Core/Ui/Page/Header/Menu',['Environment', 'Language', 'Ui/Screen'], function(Environment, Language, UiScreen) {
-       "use strict";
-       
-       var _enabled = false;
-       
-       // elements
-       var _buttonShowNext, _buttonShowPrevious, _firstElement, _menu;
-       
-       // internal states
-       var _marginLeft = 0, _invisibleLeft = [], _invisibleRight = [];
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Page/Header/Menu
-        */
-       return {
-               /**
-                * Initializes the main menu overflow handling.
-                */
-               init: function () {
-                       _menu = elBySel('.mainMenu .boxMenu');
-                       _firstElement = (_menu && _menu.childElementCount) ? _menu.children[0] : null;
-                       if (_firstElement === null) {
-                               throw new Error("Unable to find the menu.");
-                       }
-                       
-                       UiScreen.on('screen-lg', {
-                               enable: this._enable.bind(this),
-                               disable: this._disable.bind(this),
-                               setup: this._setup.bind(this)
-                       });
-               },
-               
-               /**
-                * Enables the overflow handler.
-                * 
-                * @protected
-                */
-               _enable: function () {
-                       _enabled = true;
-                       
-                       // Safari waits three seconds for a font to be loaded which causes the header menu items
-                       // to be extremely wide while waiting for the font to be loaded. The extremely wide menu
-                       // items in turn can cause the overflow controls to be shown even if the width of the header
-                       // menu, after the font has been loaded successfully, does not require them. This width
-                       // issue results in the next button being shown for a short time. To circumvent this issue,
-                       // we wait a second before showing the obverflow controls in Safari.
-                       // see https://webkit.org/blog/6643/improved-font-loading/
-                       if (Environment.browser() === 'safari') {
-                               window.setTimeout(this._rebuildVisibility.bind(this), 1000);
-                       }
-                       else {
-                               this._rebuildVisibility();
-                               
-                               // IE11 sometimes suffers from a timing issue
-                               window.setTimeout(this._rebuildVisibility.bind(this), 1000);
-                       }
-               },
-               
-               /**
-                * Disables the overflow handler.
-                * 
-                * @protected
-                */
-               _disable: function () {
-                       _enabled = false;
-               },
-               
-               /**
-                * Displays the next three menu items.
-                * 
-                * @param       {Event}         event           event object
-                * @protected
-                */
-               _showNext: function(event) {
-                       event.preventDefault();
-                       
-                       if (_invisibleRight.length) {
-                               var showItem = _invisibleRight.slice(0, 3).pop();
-                               this._setMarginLeft(_menu.clientWidth - (showItem.offsetLeft + showItem.clientWidth));
-                               
-                               if (_menu.lastElementChild === showItem) {
-                                       _buttonShowNext.classList.remove('active');
-                               }
-                               
-                               _buttonShowPrevious.classList.add('active');
-                       }
-               },
-               
-               /**
-                * Displays the previous three menu items.
-                * 
-                * @param       {Event}         event           event object
-                * @protected
-                */
-               _showPrevious: function (event) {
-                       event.preventDefault();
-                       
-                       if (_invisibleLeft.length) {
-                               var showItem = _invisibleLeft.slice(-3)[0];
-                               this._setMarginLeft(showItem.offsetLeft * -1);
-                               
-                               if (_menu.firstElementChild === showItem) {
-                                       _buttonShowPrevious.classList.remove('active');
-                               }
-                               
-                               _buttonShowNext.classList.add('active');
-                       }
-               },
-               
-               /**
-                * Sets the first item's margin-left value that is
-                * used to move the menu contents around.
-                * 
-                * @param       {int}   offset  changes to the margin-left value in pixel
-                * @protected
-                */
-               _setMarginLeft: function (offset) {
-                       _marginLeft = Math.min(_marginLeft + offset, 0);
-                       
-                       _firstElement.style.setProperty('margin-left', _marginLeft + 'px', '');
-               },
-               
-               /**
-                * Toggles button overlays and rebuilds the list
-                * of invisible items from left to right.
-                * 
-                * @protected
-                */
-               _rebuildVisibility: function () {
-                       if (!_enabled) return;
-                       
-                       _invisibleLeft = [];
-                       _invisibleRight = [];
-                       
-                       var menuWidth = _menu.clientWidth;
-                       if (_menu.scrollWidth > menuWidth || _marginLeft < 0) {
-                               var child;
-                               for (var i = 0, length = _menu.childElementCount; i < length; i++) {
-                                       child = _menu.children[i];
-                                       
-                                       var offsetLeft = child.offsetLeft;
-                                       if (offsetLeft < 0) {
-                                               _invisibleLeft.push(child);
-                                       }
-                                       else if (offsetLeft + child.clientWidth > menuWidth) {
-                                               _invisibleRight.push(child);
-                                       }
-                               }
-                       }
-                       
-                       _buttonShowPrevious.classList[(_invisibleLeft.length ? 'add' : 'remove')]('active');
-                       _buttonShowNext.classList[(_invisibleRight.length ? 'add' : 'remove')]('active');
-               },
-               
-               /**
-                * Builds the UI and binds the event listeners.
-                *
-                * @protected
-                */
-               _setup: function () {
-                       this._setupOverflow();
-                       this._setupA11y();
-               },
-               
-               /**
-                * Setups overflow handling.
-                * 
-                * @protected
-                */
-               _setupOverflow: function () {
-                       _buttonShowNext = elCreate('a');
-                       _buttonShowNext.className = 'mainMenuShowNext';
-                       _buttonShowNext.href = '#';
-                       _buttonShowNext.innerHTML = '<span class="icon icon32 fa-angle-right"></span>';
-                       _buttonShowNext.addEventListener(WCF_CLICK_EVENT, this._showNext.bind(this));
-                       
-                       _menu.parentNode.appendChild(_buttonShowNext);
-                       
-                       _buttonShowPrevious = elCreate('a');
-                       _buttonShowPrevious.className = 'mainMenuShowPrevious';
-                       _buttonShowPrevious.href = '#';
-                       _buttonShowPrevious.innerHTML = '<span class="icon icon32 fa-angle-left"></span>';
-                       _buttonShowPrevious.addEventListener(WCF_CLICK_EVENT, this._showPrevious.bind(this));
-                       
-                       _menu.parentNode.insertBefore(_buttonShowPrevious, _menu.parentNode.firstChild);
-                       
-                       var rebuildVisibility = this._rebuildVisibility.bind(this);
-                       _firstElement.addEventListener('transitionend', rebuildVisibility);
-                       
-                       window.addEventListener('resize', function () {
-                               _firstElement.style.setProperty('margin-left', '0px', '');
-                               _marginLeft = 0;
-                               
-                               rebuildVisibility();
-                       });
-                       
-                       this._enable();
-               },
-               
-               /**
-                * Setups a11y improvements.
-                *
-                * @protected
-                */
-               _setupA11y: function() {
-                       elBySelAll('.boxMenuHasChildren', _menu, (function(element) {
-                               var showMenu = false;
-                               var link = elBySel('.boxMenuLink', element);
-                               if (link) {
-                                       elAttr(link, 'aria-haspopup', true);
-                                       elAttr(link, 'aria-expanded', showMenu);
-                               }
-                               
-                               var showMenuButton = elCreate('button');
-                               showMenuButton.className = 'visuallyHidden';
-                               showMenuButton.tabindex = 0;
-                               elAttr(showMenuButton, 'role', 'button');
-                               elAttr(showMenuButton, 'aria-label', Language.get('wcf.global.button.showMenu'));
-                               element.insertBefore(showMenuButton, link.nextSibling);
-                               
-                               showMenuButton.addEventListener(WCF_CLICK_EVENT, function() {
-                                       showMenu = !showMenu;
-                                       elAttr(link, 'aria-expanded', showMenu);
-                                       elAttr(showMenuButton, 'aria-label', (showMenu ? Language.get('wcf.global.button.hideMenu') : Language.get('wcf.global.button.showMenu')));
-                               });
-                       }).bind(this));
-               }
-       };
-});
-
-/**
- * Bootstraps WCF's JavaScript with additions for the frontend usage.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/BootstrapFrontend
- */
-define(
-       'WoltLabSuite/Core/BootstrapFrontend',[
-               'WoltLabSuite/Core/BackgroundQueue', 'WoltLabSuite/Core/Bootstrap', 'WoltLabSuite/Core/Controller/Style/Changer',
-               'WoltLabSuite/Core/Controller/Popover', 'WoltLabSuite/Core/Ui/User/Ignore', 'WoltLabSuite/Core/Ui/Page/Header/Menu'
-       ],
-       function(
-               BackgroundQueue, Bootstrap, ControllerStyleChanger,
-               ControllerPopover, UiUserIgnore, UiPageHeaderMenu
-       )
-{
-       "use strict";
-       
-       /**
-        * @exports     WoltLabSuite/Core/BootstrapFrontend
-        */
-       return {
-               /**
-                * Bootstraps general modules and frontend exclusive ones.
-                * 
-                * @param       {object<string, *>}     options         bootstrap options
-                */
-               setup: function(options) {
-                       // fix the background queue URL to always run against the current domain (avoiding CORS)
-                       options.backgroundQueue.url = WSC_API_URL + options.backgroundQueue.url.substr(WCF_PATH.length);
-                       
-                       Bootstrap.setup();
-                       
-                       UiPageHeaderMenu.init();
-                       
-                       if (options.styleChanger) {
-                               ControllerStyleChanger.setup();
-                       }
-                       
-                       if (options.enableUserPopover) {
-                               this._initUserPopover();
-                       }
-                       
-                       BackgroundQueue.setUrl(options.backgroundQueue.url);
-                       if (Math.random() < 0.1 || options.backgroundQueue.force) {
-                               // invoke the queue roughly every 10th request or on demand
-                               BackgroundQueue.invoke();
-                       }
-                       
-                       if (COMPILER_TARGET_DEFAULT) {
-                               UiUserIgnore.init();
-                       }
-               },
-               
-               /**
-                * Initializes user profile popover.
-                */
-               _initUserPopover: function() {
-                       ControllerPopover.init({
-                               attributeName: 'data-user-id',
-                               className: 'userLink',
-                               identifier: 'com.woltlab.wcf.user',
-                               loadCallback: function(objectId, popover) {
-                                       var callback = function(data) {
-                                               popover.setContent('com.woltlab.wcf.user', objectId, data.returnValues.template);
-                                       };
-                                       
-                                       popover.ajaxApi({
-                                               actionName: 'getUserProfile',
-                                               className: 'wcf\\data\\user\\UserProfileAction',
-                                               objectIDs: [ objectId ]
-                                       }, callback, callback);
-                               }
-                       });
-               }
-       };
-});
-
-/**
- * Wrapper around the web browser's various clipboard APIs.
- * 
- * @author     Tim Duesterhus
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Clipboard
- */
-define('WoltLabSuite/Core/Clipboard',[], function() {
-       "use strict";
-       
-       return {
-               copyTextToClipboard: function (text) {
-                       if (navigator.clipboard) {
-                               return navigator.clipboard.writeText(text);
-                       }
-                       else if (window.getSelection) {
-                               var textarea = elCreate('textarea');
-                               textarea.contentEditable = true;
-                               textarea.readOnly = false;
-                               textarea.style.cssText = 'position: absolute; left: -9999px; top: -9999px; width: 0; height: 0;';
-                               document.body.appendChild(textarea);
-                               try {
-                                       // see: https://stackoverflow.com/a/34046084/782822
-                                       textarea.value = text;
-                                       var range = document.createRange();
-                                       range.selectNodeContents(textarea);
-                                       var selection = window.getSelection();
-                                       selection.removeAllRanges();
-                                       selection.addRange(range);
-                                       textarea.setSelectionRange(0, 999999);
-                                       if (!document.execCommand('copy')) {
-                                               return Promise.reject(new Error("execCommand('copy') failed"));
-                                       }
-                                       return Promise.resolve();
-                               }
-                               finally {
-                                       elRemove(textarea);
-                               }
-                       }
-                       
-                       return Promise.reject(new Error('Neither navigator.clipboard, nor window.getSelection is supported.'));
-               },
-               
-               copyElementTextToClipboard: function (element) {
-                       return this.copyTextToClipboard(element.textContent);
-               }
-       };
-});
-
-/**
- * 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      WoltLabSuite/Core/ColorUtil
- */
-define('WoltLabSuite/Core/ColorUtil',[], 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;
-});
-
-/**
- * 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
- */
-define('WoltLabSuite/Core/FileUtil',['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});
-               },
-       };
-});
-
-/**
- * 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     WoltLabSuite/Core/Permission
- */
-define('WoltLabSuite/Core/Permission',['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;
-               }
-       };
-});
-
-/* PrismJS 1.15.0
-https://prismjs.com/download.html#themes=prism */
-var _self = (typeof window !== 'undefined')
-       ? window   // if in browser
-       : (
-               (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope)
-               ? self // if in worker
-               : {}   // if in node js
-       );
-
-/**
- * Prism: Lightweight, robust, elegant syntax highlighting
- * MIT license http://www.opensource.org/licenses/mit-license.php/
- * @author Lea Verou http://lea.verou.me
- */
-
-var Prism = (function(){
-
-// Private helper vars
-var lang = /\blang(?:uage)?-([\w-]+)\b/i;
-var uniqueId = 0;
-
-var _ = _self.Prism = {
-       manual: _self.Prism && _self.Prism.manual,
-       disableWorkerMessageHandler: _self.Prism && _self.Prism.disableWorkerMessageHandler,
-       util: {
-               encode: function (tokens) {
-                       if (tokens instanceof Token) {
-                               return new Token(tokens.type, _.util.encode(tokens.content), tokens.alias);
-                       } else if (_.util.type(tokens) === 'Array') {
-                               return tokens.map(_.util.encode);
-                       } else {
-                               return tokens.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/\u00a0/g, ' ');
-                       }
-               },
-
-               type: function (o) {
-                       return Object.prototype.toString.call(o).match(/\[object (\w+)\]/)[1];
-               },
-
-               objId: function (obj) {
-                       if (!obj['__id']) {
-                               Object.defineProperty(obj, '__id', { value: ++uniqueId });
-                       }
-                       return obj['__id'];
-               },
-
-               // Deep clone a language definition (e.g. to extend it)
-               clone: function (o, visited) {
-                       var type = _.util.type(o);
-                       visited = visited || {};
-
-                       switch (type) {
-                               case 'Object':
-                                       if (visited[_.util.objId(o)]) {
-                                               return visited[_.util.objId(o)];
-                                       }
-                                       var clone = {};
-                                       visited[_.util.objId(o)] = clone;
-
-                                       for (var key in o) {
-                                               if (o.hasOwnProperty(key)) {
-                                                       clone[key] = _.util.clone(o[key], visited);
-                                               }
-                                       }
-
-                                       return clone;
-
-                               case 'Array':
-                                       if (visited[_.util.objId(o)]) {
-                                               return visited[_.util.objId(o)];
-                                       }
-                                       var clone = [];
-                                       visited[_.util.objId(o)] = clone;
-
-                                       o.forEach(function (v, i) {
-                                               clone[i] = _.util.clone(v, visited);
-                                       });
-
-                                       return clone;
-                       }
-
-                       return o;
-               }
-       },
-
-       languages: {
-               extend: function (id, redef) {
-                       var lang = _.util.clone(_.languages[id]);
-
-                       for (var key in redef) {
-                               lang[key] = redef[key];
-                       }
-
-                       return lang;
-               },
-
-               /**
-                * Insert a token before another token in a language literal
-                * As this needs to recreate the object (we cannot actually insert before keys in object literals),
-                * we cannot just provide an object, we need anobject and a key.
-                * @param inside The key (or language id) of the parent
-                * @param before The key to insert before. If not provided, the function appends instead.
-                * @param insert Object with the key/value pairs to insert
-                * @param root The object that contains `inside`. If equal to Prism.languages, it can be omitted.
-                */
-               insertBefore: function (inside, before, insert, root) {
-                       root = root || _.languages;
-                       var grammar = root[inside];
-
-                       if (arguments.length == 2) {
-                               insert = arguments[1];
-
-                               for (var newToken in insert) {
-                                       if (insert.hasOwnProperty(newToken)) {
-                                               grammar[newToken] = insert[newToken];
-                                       }
-                               }
-
-                               return grammar;
-                       }
-
-                       var ret = {};
-
-                       for (var token in grammar) {
-
-                               if (grammar.hasOwnProperty(token)) {
-
-                                       if (token == before) {
-
-                                               for (var newToken in insert) {
-
-                                                       if (insert.hasOwnProperty(newToken)) {
-                                                               ret[newToken] = insert[newToken];
-                                                       }
-                                               }
-                                       }
-
-                                       ret[token] = grammar[token];
-                               }
-                       }
-
-                       var old = root[inside];
-                       root[inside] = ret;
-
-                       // Update references in other language definitions
-                       _.languages.DFS(_.languages, function(key, value) {
-                               if (value === old && key != inside) {
-                                       this[key] = ret;
-                               }
-                       });
-
-                       return ret;
-               },
-
-               // Traverse a language definition with Depth First Search
-               DFS: function(o, callback, type, visited) {
-                       visited = visited || {};
-                       for (var i in o) {
-                               if (o.hasOwnProperty(i)) {
-                                       callback.call(o, i, o[i], type || i);
-
-                                       if (_.util.type(o[i]) === 'Object' && !visited[_.util.objId(o[i])]) {
-                                               visited[_.util.objId(o[i])] = true;
-                                               _.languages.DFS(o[i], callback, null, visited);
-                                       }
-                                       else if (_.util.type(o[i]) === 'Array' && !visited[_.util.objId(o[i])]) {
-                                               visited[_.util.objId(o[i])] = true;
-                                               _.languages.DFS(o[i], callback, i, visited);
-                                       }
-                               }
-                       }
-               }
-       },
-       plugins: {},
-
-       highlightAll: function(async, callback) {
-               _.highlightAllUnder(document, async, callback);
-       },
-
-       highlightAllUnder: function(container, async, callback) {
-               var env = {
-                       callback: callback,
-                       selector: 'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'
-               };
-
-               _.hooks.run("before-highlightall", env);
-
-               var elements = env.elements || container.querySelectorAll(env.selector);
-
-               for (var i=0, element; element = elements[i++];) {
-                       _.highlightElement(element, async === true, env.callback);
-               }
-       },
-
-       highlightElement: function(element, async, callback) {
-               // Find language
-               var language, grammar, parent = element;
-
-               while (parent && !lang.test(parent.className)) {
-                       parent = parent.parentNode;
-               }
-
-               if (parent) {
-                       language = (parent.className.match(lang) || [,''])[1].toLowerCase();
-                       grammar = _.languages[language];
-               }
-
-               // Set language on the element, if not present
-               element.className = element.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;
-
-               if (element.parentNode) {
-                       // Set language on the parent, for styling
-                       parent = element.parentNode;
-
-                       if (/pre/i.test(parent.nodeName)) {
-                               parent.className = parent.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;
-                       }
-               }
-
-               var code = element.textContent;
-
-               var env = {
-                       element: element,
-                       language: language,
-                       grammar: grammar,
-                       code: code
-               };
-
-               _.hooks.run('before-sanity-check', env);
-
-               if (!env.code || !env.grammar) {
-                       if (env.code) {
-                               _.hooks.run('before-highlight', env);
-                               env.element.textContent = env.code;
-                               _.hooks.run('after-highlight', env);
-                       }
-                       _.hooks.run('complete', env);
-                       return;
-               }
-
-               _.hooks.run('before-highlight', env);
-
-               if (async && _self.Worker) {
-                       var worker = new Worker(_.filename);
-
-                       worker.onmessage = function(evt) {
-                               env.highlightedCode = evt.data;
-
-                               _.hooks.run('before-insert', env);
-
-                               env.element.innerHTML = env.highlightedCode;
-
-                               callback && callback.call(env.element);
-                               _.hooks.run('after-highlight', env);
-                               _.hooks.run('complete', env);
-                       };
-
-                       worker.postMessage(JSON.stringify({
-                               language: env.language,
-                               code: env.code,
-                               immediateClose: true
-                       }));
-               }
-               else {
-                       env.highlightedCode = _.highlight(env.code, env.grammar, env.language);
-
-                       _.hooks.run('before-insert', env);
-
-                       env.element.innerHTML = env.highlightedCode;
-
-                       callback && callback.call(element);
-
-                       _.hooks.run('after-highlight', env);
-                       _.hooks.run('complete', env);
-               }
-       },
-
-       highlight: function (text, grammar, language) {
-               var env = {
-                       code: text,
-                       grammar: grammar,
-                       language: language
-               };
-               _.hooks.run('before-tokenize', env);
-               env.tokens = _.tokenize(env.code, env.grammar);
-               _.hooks.run('after-tokenize', env);
-               return Token.stringify(_.util.encode(env.tokens), env.language);
-       },
-
-       matchGrammar: function (text, strarr, grammar, index, startPos, oneshot, target) {
-               var Token = _.Token;
-
-               for (var token in grammar) {
-                       if(!grammar.hasOwnProperty(token) || !grammar[token]) {
-                               continue;
-                       }
-
-                       if (token == target) {
-                               return;
-                       }
-
-                       var patterns = grammar[token];
-                       patterns = (_.util.type(patterns) === "Array") ? patterns : [patterns];
-
-                       for (var j = 0; j < patterns.length; ++j) {
-                               var pattern = patterns[j],
-                                       inside = pattern.inside,
-                                       lookbehind = !!pattern.lookbehind,
-                                       greedy = !!pattern.greedy,
-                                       lookbehindLength = 0,
-                                       alias = pattern.alias;
-
-                               if (greedy && !pattern.pattern.global) {
-                                       // Without the global flag, lastIndex won't work
-                                       var flags = pattern.pattern.toString().match(/[imuy]*$/)[0];
-                                       pattern.pattern = RegExp(pattern.pattern.source, flags + "g");
-                               }
-
-                               pattern = pattern.pattern || pattern;
-
-                               // Don’t cache length as it changes during the loop
-                               for (var i = index, pos = startPos; i < strarr.length; pos += strarr[i].length, ++i) {
-
-                                       var str = strarr[i];
-
-                                       if (strarr.length > text.length) {
-                                               // Something went terribly wrong, ABORT, ABORT!
-                                               return;
-                                       }
-
-                                       if (str instanceof Token) {
-                                               continue;
-                                       }
-
-                                       if (greedy && i != strarr.length - 1) {
-                                               pattern.lastIndex = pos;
-                                               var match = pattern.exec(text);
-                                               if (!match) {
-                                                       break;
-                                               }
-
-                                               var from = match.index + (lookbehind ? match[1].length : 0),
-                                                   to = match.index + match[0].length,
-                                                   k = i,
-                                                   p = pos;
-
-                                               for (var len = strarr.length; k < len && (p < to || (!strarr[k].type && !strarr[k - 1].greedy)); ++k) {
-                                                       p += strarr[k].length;
-                                                       // Move the index i to the element in strarr that is closest to from
-                                                       if (from >= p) {
-                                                               ++i;
-                                                               pos = p;
-                                                       }
-                                               }
-
-                                               // If strarr[i] is a Token, then the match starts inside another Token, which is invalid
-                                               if (strarr[i] instanceof Token) {
-                                                       continue;
-                                               }
-
-                                               // Number of tokens to delete and replace with the new match
-                                               delNum = k - i;
-                                               str = text.slice(pos, p);
-                                               match.index -= pos;
-                                       } else {
-                                               pattern.lastIndex = 0;
-
-                                               var match = pattern.exec(str),
-                                                       delNum = 1;
-                                       }
-
-                                       if (!match) {
-                                               if (oneshot) {
-                                                       break;
-                                               }
-
-                                               continue;
-                                       }
-
-                                       if(lookbehind) {
-                                               lookbehindLength = match[1] ? match[1].length : 0;
-                                       }
-
-                                       var from = match.index + lookbehindLength,
-                                           match = match[0].slice(lookbehindLength),
-                                           to = from + match.length,
-                                           before = str.slice(0, from),
-                                           after = str.slice(to);
-
-                                       var args = [i, delNum];
-
-                                       if (before) {
-                                               ++i;
-                                               pos += before.length;
-                                               args.push(before);
-                                       }
-
-                                       var wrapped = new Token(token, inside? _.tokenize(match, inside) : match, alias, match, greedy);
-
-                                       args.push(wrapped);
-
-                                       if (after) {
-                                               args.push(after);
-                                       }
-
-                                       Array.prototype.splice.apply(strarr, args);
-
-                                       if (delNum != 1)
-                                               _.matchGrammar(text, strarr, grammar, i, pos, true, token);
-
-                                       if (oneshot)
-                                               break;
-                               }
-                       }
-               }
-       },
-
-       tokenize: function(text, grammar, language) {
-               var strarr = [text];
-
-               var rest = grammar.rest;
-
-               if (rest) {
-                       for (var token in rest) {
-                               grammar[token] = rest[token];
-                       }
-
-                       delete grammar.rest;
-               }
-
-               _.matchGrammar(text, strarr, grammar, 0, 0, false);
-
-               return strarr;
-       },
-
-       hooks: {
-               all: {},
-
-               add: function (name, callback) {
-                       var hooks = _.hooks.all;
-
-                       hooks[name] = hooks[name] || [];
-
-                       hooks[name].push(callback);
-               },
-
-               run: function (name, env) {
-                       var callbacks = _.hooks.all[name];
-
-                       if (!callbacks || !callbacks.length) {
-                               return;
-                       }
-
-                       for (var i=0, callback; callback = callbacks[i++];) {
-                               callback(env);
-                       }
-               }
-       }
-};
-
-var Token = _.Token = function(type, content, alias, matchedStr, greedy) {
-       this.type = type;
-       this.content = content;
-       this.alias = alias;
-       // Copy of the full string this token was created from
-       this.length = (matchedStr || "").length|0;
-       this.greedy = !!greedy;
-};
-
-Token.stringify = function(o, language, parent) {
-       if (typeof o == 'string') {
-               return o;
-       }
-
-       if (_.util.type(o) === 'Array') {
-               return o.map(function(element) {
-                       return Token.stringify(element, language, o);
-               }).join('');
-       }
-
-       var env = {
-               type: o.type,
-               content: Token.stringify(o.content, language, parent),
-               tag: 'span',
-               classes: ['token', o.type],
-               attributes: {},
-               language: language,
-               parent: parent
-       };
-
-       if (o.alias) {
-               var aliases = _.util.type(o.alias) === 'Array' ? o.alias : [o.alias];
-               Array.prototype.push.apply(env.classes, aliases);
-       }
-
-       _.hooks.run('wrap', env);
-
-       var attributes = Object.keys(env.attributes).map(function(name) {
-               return name + '="' + (env.attributes[name] || '').replace(/"/g, '&quot;') + '"';
-       }).join(' ');
-
-       return '<' + env.tag + ' class="' + env.classes.join(' ') + '"' + (attributes ? ' ' + attributes : '') + '>' + env.content + '</' + env.tag + '>';
-
-};
-
-if (!_self.document) {
-       if (!_self.addEventListener) {
-               // in Node.js
-               return _self.Prism;
-       }
-
-       if (!_.disableWorkerMessageHandler) {
-               // In worker
-               _self.addEventListener('message', function (evt) {
-                       var message = JSON.parse(evt.data),
-                               lang = message.language,
-                               code = message.code,
-                               immediateClose = message.immediateClose;
-
-                       _self.postMessage(_.highlight(code, _.languages[lang], lang));
-                       if (immediateClose) {
-                               _self.close();
-                       }
-               }, false);
-       }
-
-       return _self.Prism;
-}
-
-//Get current script and highlight
-var script = document.currentScript || [].slice.call(document.getElementsByTagName("script")).pop();
-
-if (script) {
-       _.filename = script.src;
-
-       if (!_.manual && !script.hasAttribute('data-manual')) {
-               if(document.readyState !== "loading") {
-                       if (window.requestAnimationFrame) {
-                               window.requestAnimationFrame(_.highlightAll);
-                       } else {
-                               window.setTimeout(_.highlightAll, 16);
-                       }
-               }
-               else {
-                       document.addEventListener('DOMContentLoaded', _.highlightAll);
-               }
-       }
-}
-
-return _self.Prism;
-
-})();
-
-if (typeof module !== 'undefined' && module.exports) {
-       module.exports = Prism;
-}
-
-// hack for components to work correctly in node.js
-if (typeof global !== 'undefined') {
-       global.Prism = Prism;
-}
-;
-
-define("prism/prism", function(){});
-
-/**
- * Augments the Prism syntax highlighter with additional functions.
- * 
- * @author     Tim Duesterhus
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Prism
- */
-
-window.Prism = window.Prism || {}
-window.Prism.manual = true
-
-define('WoltLabSuite/Core/Prism',['prism/prism'], function () {
-       Prism.wscSplitIntoLines = function (container) {
-               var frag = document.createDocumentFragment();
-               var lineNo = 1;
-               var it, node, line;
-               
-               function newLine() {
-                       var line = elCreate('span');
-                       elData(line, 'number', lineNo++);
-                       frag.appendChild(line);
-                       
-                       return line;
-               }
-               
-               // IE11 expects a fourth, non-standard, parameter (entityReferenceExpansion) and a valid function as third
-               it = document.createNodeIterator(container, NodeFilter.SHOW_TEXT, function () {
-                       return NodeFilter.FILTER_ACCEPT;
-               }, false);
-               
-               line = newLine(lineNo);
-               while (node = it.nextNode()) {
-                       node.data.split(/\r?\n/).forEach(function (codeLine, index) {
-                               var current, parent;
-                               
-                               // We are behind a newline, insert \n and create new container.
-                               if (index >= 1) {
-                                       line.appendChild(document.createTextNode("\n"));
-                                       line = newLine(lineNo);
-                               }
-                               
-                               current = document.createTextNode(codeLine);
-                               
-                               // Copy hierarchy (to preserve CSS classes).
-                               parent = node.parentNode
-                               while (parent !== container) {
-                                       var clone = parent.cloneNode(false);
-                                       clone.appendChild(current);
-                                       current = clone;
-                                       parent = parent.parentNode;
-                               }
-                               
-                               line.appendChild(current);
-                       });
-               }
-               
-               return frag;
-       };
-
-       return Prism;
-});
-
-/**
- * Uploads file via AJAX.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Upload
- */
-define('WoltLabSuite/Core/Upload',['AjaxRequest', 'Core', 'Dom/ChangeListener', 'Language', 'Dom/Util', 'Dom/Traverse'], function(AjaxRequest, Core, DomChangeListener, Language, DomUtil, DomTraverse) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       _createButton: function() {},
-                       _createFileElement: function() {},
-                       _createFileElements: function() {},
-                       _failure: function() {},
-                       _getParameters: function() {},
-                       _insertButton: function() {},
-                       _progress: function() {},
-                       _removeButton: function() {},
-                       _success: function() {},
-                       _upload: function() {},
-                       _uploadFiles: function() {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * @constructor
-        */
-       function Upload(buttonContainerId, targetId, options) {
-               options = options || {};
-               
-               if (options.className === undefined) {
-                       throw new Error("Missing class name.");
-               }
-               
-               // set default options
-               this._options = Core.extend({
-                       // name of the PHP action
-                       action: 'upload',
-                       // is true if multiple files can be uploaded at once
-                       multiple: false,
-                       // name if the upload field
-                       name: '__files[]',
-                       // is true if every file from a multi-file selection is uploaded in its own request
-                       singleFileRequests: false,
-                       // url for uploading file
-                       url: 'index.php?ajax-upload/&t=' + SECURITY_TOKEN
-               }, options);
-               
-               this._options.url = Core.convertLegacyUrl(this._options.url);
-               if (this._options.url.indexOf('index.php') === 0) {
-                       this._options.url = WSC_API_URL + this._options.url;
-               }
-               
-               this._buttonContainer = elById(buttonContainerId);
-               if (this._buttonContainer === null) {
-                       throw new Error("Element id '" + buttonContainerId + "' is unknown.");
-               }
-               
-               this._target = elById(targetId);
-               if (targetId === null) {
-                       throw new Error("Element id '" + targetId + "' is unknown.");
-               }
-               if (options.multiple && this._target.nodeName !== 'UL' && this._target.nodeName !== 'OL' && this._target.nodeName !== 'TBODY') {
-                       throw new Error("Target element has to be list or table body if uploading multiple files is supported.");
-               }
-               
-               this._fileElements = [];
-               this._internalFileId = 0;
-               
-               // upload ids that belong to an upload of multiple files at once
-               this._multiFileUploadIds = [];
-               
-               this._createButton();
-       }
-       Upload.prototype = {
-               /**
-                * Creates the upload button.
-                */
-               _createButton: function() {
-                       this._fileUpload = elCreate('input');
-                       elAttr(this._fileUpload, 'type', 'file');
-                       elAttr(this._fileUpload, 'name', this._options.name);
-                       if (this._options.multiple) {
-                               elAttr(this._fileUpload, 'multiple', 'true');
-                       }
-                       this._fileUpload.addEventListener('change', this._upload.bind(this));
-                       
-                       this._button = elCreate('p');
-                       this._button.className = 'button uploadButton';
-                       elAttr(this._button, 'role', 'button');
-                       elAttr(this._button, 'tabindex', '0');
-                       
-                       var span = elCreate('span');
-                       span.textContent = Language.get('wcf.global.button.upload');
-                       this._button.appendChild(span);
-                       
-                       DomUtil.prepend(this._fileUpload, this._button);
-                       
-                       this._insertButton();
-                       
-                       DomChangeListener.trigger();
-               },
-               
-               /**
-                * Creates the document element for an uploaded file.
-                * 
-                * @param       {File}          file            uploaded file
-                * @return      {HTMLElement}
-                */
-               _createFileElement: function(file) {
-                       var progress = elCreate('progress');
-                       elAttr(progress, 'max', 100);
-                       
-                       if (this._target.nodeName === 'OL' || this._target.nodeName === 'UL') {
-                               var li = elCreate('li');
-                               li.innerText = file.name;
-                               li.appendChild(progress);
-                               
-                               this._target.appendChild(li);
-                               
-                               return li;
-                       }
-                       else if (this._target.nodeName === 'TBODY') {
-                               return this._createFileTableRow(file);
-                       }
-                       else {
-                               var p = elCreate('p');
-                               p.appendChild(progress);
-                               
-                               this._target.appendChild(p);
-                               
-                               return p;
-                       }
-               },
-               
-               /**
-                * Creates the document elements for uploaded files.
-                * 
-                * @param       {(FileList|Array.<File>)}       files           uploaded files
-                */
-               _createFileElements: function(files) {
-                       if (files.length) {
-                               var uploadId = this._fileElements.length;
-                               this._fileElements[uploadId] = [];
-                               
-                               for (var i = 0, length = files.length; i < length; i++) {
-                                       var file = files[i];
-                                       var fileElement = this._createFileElement(file);
-                                       
-                                       if (!fileElement.classList.contains('uploadFailed')) {
-                                               elData(fileElement, 'filename', file.name);
-                                               elData(fileElement, 'internal-file-id', this._internalFileId++);
-                                               this._fileElements[uploadId][i] = fileElement;
-                                       }
-                               }
-                               
-                               DomChangeListener.trigger();
-                               
-                               return uploadId;
-                       }
-                       
-                       return null;
-               },
-               
-               _createFileTableRow: function(file) {
-                       throw new Error("Has to be implemented in subclass.");
-               },
-               
-               /**
-                * Handles a failed file upload.
-                * 
-                * @param       {int}                   uploadId        identifier of a file upload
-                * @param       {object<string, *>}     data            response data
-                * @param       {string}                responseText    response
-                * @param       {XMLHttpRequest}        xhr             request object
-                * @param       {object<string, *>}     requestOptions  options used to send AJAX request
-                * @return      {boolean}       true if the error message should be shown
-                */
-               _failure: function(uploadId, data, responseText, xhr, requestOptions) {
-                       // does nothing
-                       return true;
-               },
-               
-               /**
-                * Return additional parameters for upload requests.
-                * 
-                * @return      {object<string, *>}     additional parameters
-                */
-               _getParameters: function() {
-                       return {};
-               },
-               
-               /**
-                * Return additional form data for upload requests.
-                * 
-                * @return      {object<string, *>}     additional form data
-                * @since       5.2
-                */
-               _getFormData: function() {
-                       return {};
-               },
-               
-               /**
-                * Inserts the created button to upload files into the button container.
-                */
-               _insertButton: function() {
-                       DomUtil.prepend(this._button, this._buttonContainer);
-               },
-               
-               /**
-                * Updates the progress of an upload.
-                * 
-                * @param       {int}                           uploadId        internal upload identifier
-                * @param       {XMLHttpRequestProgressEvent}   event           progress event object
-                */
-               _progress: function(uploadId, event) {
-                       var percentComplete = Math.round(event.loaded / event.total * 100);
-                       
-                       for (var i in this._fileElements[uploadId]) {
-                               var progress = elByTag('PROGRESS', this._fileElements[uploadId][i]);
-                               if (progress.length === 1) {
-                                       elAttr(progress[0], 'value', percentComplete);
-                               }
-                       }
-               },
-               
-               /**
-                * Removes the button to upload files.
-                */
-               _removeButton: function() {
-                       elRemove(this._button);
-                       
-                       DomChangeListener.trigger();
-               },
-               
-               /**
-                * Handles a successful file upload.
-                * 
-                * @param       {int}                   uploadId        identifier of a file upload
-                * @param       {object<string, *>}     data            response data
-                * @param       {string}                responseText    response
-                * @param       {XMLHttpRequest}        xhr             request object
-                * @param       {object<string, *>}     requestOptions  options used to send AJAX request
-                */
-               _success: function(uploadId, data, responseText, xhr, requestOptions) {
-                       // does nothing
-               },
-               
-               /**
-                * File input change callback to upload files.
-                * 
-                * @param       {Event}         event           input change event object
-                * @param       {File}          file            uploaded file
-                * @param       {Blob}          blob            file blob
-                * @return      {(int|Array.<int>|null)}        identifier(s) for the uploaded files
-                */
-               _upload: function(event, file, blob) {
-                       // remove failed upload elements first
-                       var failedUploads = DomTraverse.childrenByClass(this._target, 'uploadFailed');
-                       for (var i = 0, length = failedUploads.length; i < length; i++) {
-                               elRemove(failedUploads[i]);
-                       }
-                       
-                       var uploadId = null;
-                       
-                       var files = [];
-                       if (file) {
-                               files.push(file);
-                       }
-                       else if (blob) {
-                               var fileExtension = '';
-                               switch (blob.type) {
-                                       case 'image/jpeg':
-                                               fileExtension = '.jpg';
-                                       break;
-                                       
-                                       case 'image/gif':
-                                               fileExtension = '.gif';
-                                       break;
-                                       
-                                       case 'image/png':
-                                               fileExtension = '.png';
-                                       break;
-                               }
-                               
-                               files.push({
-                                       name: 'pasted-from-clipboard' + fileExtension
-                               });
-                       }
-                       else {
-                               files = this._fileUpload.files;
-                       }
-                       
-                       if (files.length && this.validateUpload(files)) {
-                               if (this._options.singleFileRequests) {
-                                       uploadId = [];
-                                       for (var i = 0, length = files.length; i < length; i++) {
-                                               var localUploadId = this._uploadFiles([ files[i] ], blob);
-                                               
-                                               if (files.length !== 1) {
-                                                       this._multiFileUploadIds.push(localUploadId)
-                                               }
-                                               uploadId.push(localUploadId);
-                                       }
-                               }
-                               else {
-                                       uploadId = this._uploadFiles(files, blob);
-                               }
-                       }
-                       
-                       // re-create upload button to effectively reset the 'files'
-                       // property of the input element
-                       this._removeButton();
-                       this._createButton();
-                       
-                       return uploadId;
-               },
-               
-               /**
-                * Validates the upload before uploading them.
-                * 
-                * @param       {(FileList|Array.<File>)}       files           uploaded files
-                * @return      {boolean}
-                * @since       5.2
-                */
-               validateUpload: function(files) {
-                       return true;
-               },
-               
-               /**
-                * Sends the request to upload files.
-                * 
-                * @param       {(FileList|Array.<File>)}       files           uploaded files
-                * @param       {Blob}                          blob            file blob
-                * @return      {(int|null)}    identifier for the uploaded files
-                */
-               _uploadFiles: function(files, blob) {
-                       var uploadId = this._createFileElements(files);
-                       
-                       // no more files left, abort
-                       if (!this._fileElements[uploadId].length) {
-                               return null;
-                       }
-                       
-                       var formData = new FormData();
-                       for (var i = 0, length = files.length; i < length; i++) {
-                               if (this._fileElements[uploadId][i]) {
-                                       var internalFileId = elData(this._fileElements[uploadId][i], 'internal-file-id');
-                                       
-                                       if (blob) {
-                                               formData.append('__files[' + internalFileId + ']', blob, files[i].name);
-                                       }
-                                       else {
-                                               formData.append('__files[' + internalFileId + ']', files[i]);
-                                       }
-                               }
-                       }
-                       
-                       formData.append('actionName', this._options.action);
-                       formData.append('className', this._options.className);
-                       if (this._options.action === 'upload') {
-                               formData.append('interfaceName', 'wcf\\data\\IUploadAction');
-                       }
-                       
-                       // recursively append additional parameters to form data
-                       var appendFormData = function(parameters, prefix) {
-                               prefix = prefix || '';
-                               
-                               for (var name in parameters) {
-                                       if (typeof parameters[name] === 'object') {
-                                               var newPrefix = prefix.length === 0 ? name : prefix + '[' + name + ']';
-                                               appendFormData(parameters[name], newPrefix);
-                                       }
-                                       else {
-                                               var dataName = prefix.length === 0 ? name : prefix + '[' + name + ']';
-                                               formData.append(dataName, parameters[name]);
-                                       }
-                               }
-                       };
-                       
-                       appendFormData(this._getParameters(), 'parameters');
-                       appendFormData(this._getFormData());
-                       
-                       var request = new AjaxRequest({
-                               data: formData,
-                               contentType: false,
-                               failure: this._failure.bind(this, uploadId),
-                               silent: true,
-                               success: this._success.bind(this, uploadId),
-                               uploadProgress: this._progress.bind(this, uploadId),
-                               url: this._options.url,
-                               withCredentials: true
-                       });
-                       request.sendRequest();
-                       
-                       return uploadId;
-               },
-               
-               /**
-                * Returns true if there are any pending uploads handled by this
-                * upload manager.
-                * 
-                * @return      {boolean}
-                * @since       5.2
-                */
-               hasPendingUploads: function() {
-                       for (var uploadId in this._fileElements) {
-                               for (var i in this._fileElements[uploadId]) {
-                                       var progress = elByTag('PROGRESS', this._fileElements[uploadId][i]);
-                                       if (progress.length === 1) {
-                                               return true;
-                                       }
-                               }
-                       }
-                       
-                       return false;
-               },
-               
-               /**
-                * Uploads the given file blob.
-                * 
-                * @param       {Blob}          blob            file blob
-                * @return      {int}           identifier for the uploaded file
-                */
-               uploadBlob: function(blob) {
-                       return this._upload(null, null, blob);
-               },
-               
-               /**
-                * Uploads the given file.
-                *
-                * @param       {File}          file            uploaded file
-                * @return      {int}           identifier(s) for the uploaded file
-                */
-               uploadFile: function(file) {
-                       return this._upload(null, file);
-               }
-       };
-       
-       return Upload;
-});
-
-/**
- * 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     WoltLabSuite/Core/User
- */
-define('WoltLabSuite/Core/User',[], 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;
-               }
-       };
-});
-
-/**
- * Provides a utility class to issue JSONP requests.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ajax/Jsonp
- */
-define('WoltLabSuite/Core/Ajax/Jsonp',['Core'], function(Core) {
-       "use strict";
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ajax/Jsonp
-        */
-       return {
-               /**
-                * Issues a JSONP request.
-                * 
-                * @param       {string}                url             source URL, must not contain callback parameter
-                * @param       {function}              success         success callback
-                * @param       {function=}             failure         timeout callback
-                * @param       {object<string, *>=}    options         request options
-                */
-               send: function(url, success, failure, options) {
-                       url = (typeof url === 'string') ? url.trim() : '';
-                       if (url.length === 0) {
-                               throw new Error("Expected a non-empty string for parameter 'url'.");
-                       }
-                       
-                       if (typeof success !== 'function') {
-                               throw new TypeError("Expected a valid callback function for parameter 'success'.");
-                       }
-                       
-                       options = Core.extend({
-                               parameterName: 'callback',
-                               timeout: 10
-                       }, options || {});
-                       
-                       var callbackName = 'wcf_jsonp_' + Core.getUuid().replace(/-/g, '').substr(0, 8);
-                       var script;
-                       
-                       var timeout = window.setTimeout(function() {
-                               if (typeof failure === 'function') {
-                                       failure();
-                               }
-                               
-                               window[callbackName] = undefined;
-                               elRemove(script);
-                       }, (~~options.timeout || 10) * 1000);
-                       
-                       window[callbackName] = function() {
-                               window.clearTimeout(timeout);
-                               
-                               success.apply(null, arguments);
-                               
-                               window[callbackName] = undefined;
-                               elRemove(script);
-                       };
-                       
-                       url += (url.indexOf('?') === -1) ? '?' : '&';
-                       url += options.parameterName + '=' + callbackName;
-                       
-                       script = elCreate('script');
-                       script.async = true;
-                       elAttr(script, 'src', url);
-                       
-                       document.head.appendChild(script);
-               }
-       };
-});
-
-/**
- * Simple notification overlay.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Notification
- */
-define('WoltLabSuite/Core/Ui/Notification',['Language'], function(Language) {
-       "use strict";
-       
-       var _busy = false;
-       var _callback = null;
-       var _message = null;
-       var _notificationElement = null;
-       var _timeout = null;
-       
-       var _callbackHide = null;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Notification
-        */
-       var UiNotification = {
-               /**
-                * Shows a notification.
-                * 
-                * @param       {string}        message         message
-                * @param       {function=}     callback        callback function to be executed once notification is being hidden
-                * @param       {string=}       cssClassName    alternate CSS class name, defaults to 'success'
-                */
-               show: function(message, callback, cssClassName) {
-                       if (_busy) {
-                               return;
-                       }
-                       
-                       this._init();
-                       
-                       _callback = (typeof callback === 'function') ? callback : null;
-                       _message.className = cssClassName || 'success';
-                       _message.textContent = Language.get(message || 'wcf.global.success');
-                       
-                       _busy = true;
-                       
-                       _notificationElement.classList.add('active');
-                       
-                       _timeout = setTimeout(_callbackHide, 2000);
-               },
-               
-               /**
-                * Initializes the UI elements.
-                */
-               _init: function() {
-                       if (_notificationElement === null) {
-                               _callbackHide = this._hide.bind(this);
-                               
-                               _notificationElement = elCreate('div');
-                               _notificationElement.id = 'systemNotification';
-                               
-                               _message = elCreate('p');
-                               _message.addEventListener(WCF_CLICK_EVENT, _callbackHide);
-                               _notificationElement.appendChild(_message);
-                               
-                               document.body.appendChild(_notificationElement);
-                       }
-               },
-               
-               /**
-                * Hides the notification and invokes the callback if provided.
-                */
-               _hide: function() {
-                       clearTimeout(_timeout);
-                       
-                       _notificationElement.classList.remove('active');
-                       
-                       if (_callback !== null) {
-                               _callback();
-                       }
-                       
-                       _busy = false;
-               }
-       };
-       
-       return UiNotification;
-});
-
-define('prism/prism-meta',[],function(){return /*START*/{"markup":{"title":"Markup","file":"markup"},"html":{"title":"HTML","file":"markup"},"xml":{"title":"XML","file":"markup"},"svg":{"title":"SVG","file":"markup"},"mathml":{"title":"MathML","file":"markup"},"css":{"title":"CSS","file":"css"},"clike":{"title":"C-like","file":"clike"},"javascript":{"title":"JavaScript","file":"javascript"},"abap":{"title":"ABAP","file":"abap"},"actionscript":{"title":"ActionScript","file":"actionscript"},"ada":{"title":"Ada","file":"ada"},"apacheconf":{"title":"Apache Configuration","file":"apacheconf"},"apl":{"title":"APL","file":"apl"},"applescript":{"title":"AppleScript","file":"applescript"},"arduino":{"title":"Arduino","file":"arduino"},"arff":{"title":"ARFF","file":"arff"},"asciidoc":{"title":"AsciiDoc","file":"asciidoc"},"asm6502":{"title":"6502 Assembly","file":"asm6502"},"aspnet":{"title":"ASP.NET (C#)","file":"aspnet"},"autohotkey":{"title":"AutoHotkey","file":"autohotkey"},"autoit":{"title":"AutoIt","file":"autoit"},"bash":{"title":"Bash","file":"bash"},"basic":{"title":"BASIC","file":"basic"},"batch":{"title":"Batch","file":"batch"},"bison":{"title":"Bison","file":"bison"},"brainfuck":{"title":"Brainfuck","file":"brainfuck"},"bro":{"title":"Bro","file":"bro"},"c":{"title":"C","file":"c"},"csharp":{"title":"C#","file":"csharp"},"cpp":{"title":"C++","file":"cpp"},"coffeescript":{"title":"CoffeeScript","file":"coffeescript"},"clojure":{"title":"Clojure","file":"clojure"},"crystal":{"title":"Crystal","file":"crystal"},"csp":{"title":"Content-Security-Policy","file":"csp"},"css-extras":{"title":"CSS Extras","file":"css-extras"},"d":{"title":"D","file":"d"},"dart":{"title":"Dart","file":"dart"},"diff":{"title":"Diff","file":"diff"},"django":{"title":"Django/Jinja2","file":"django"},"docker":{"title":"Docker","file":"docker"},"eiffel":{"title":"Eiffel","file":"eiffel"},"elixir":{"title":"Elixir","file":"elixir"},"elm":{"title":"Elm","file":"elm"},"erb":{"title":"ERB","file":"erb"},"erlang":{"title":"Erlang","file":"erlang"},"fsharp":{"title":"F#","file":"fsharp"},"flow":{"title":"Flow","file":"flow"},"fortran":{"title":"Fortran","file":"fortran"},"gedcom":{"title":"GEDCOM","file":"gedcom"},"gherkin":{"title":"Gherkin","file":"gherkin"},"git":{"title":"Git","file":"git"},"glsl":{"title":"GLSL","file":"glsl"},"gml":{"title":"GameMaker Language","file":"gml"},"go":{"title":"Go","file":"go"},"graphql":{"title":"GraphQL","file":"graphql"},"groovy":{"title":"Groovy","file":"groovy"},"haml":{"title":"Haml","file":"haml"},"handlebars":{"title":"Handlebars","file":"handlebars"},"haskell":{"title":"Haskell","file":"haskell"},"haxe":{"title":"Haxe","file":"haxe"},"http":{"title":"HTTP","file":"http"},"hpkp":{"title":"HTTP Public-Key-Pins","file":"hpkp"},"hsts":{"title":"HTTP Strict-Transport-Security","file":"hsts"},"ichigojam":{"title":"IchigoJam","file":"ichigojam"},"icon":{"title":"Icon","file":"icon"},"inform7":{"title":"Inform 7","file":"inform7"},"ini":{"title":"Ini","file":"ini"},"io":{"title":"Io","file":"io"},"j":{"title":"J","file":"j"},"java":{"title":"Java","file":"java"},"jolie":{"title":"Jolie","file":"jolie"},"json":{"title":"JSON","file":"json"},"julia":{"title":"Julia","file":"julia"},"keyman":{"title":"Keyman","file":"keyman"},"kotlin":{"title":"Kotlin","file":"kotlin"},"latex":{"title":"LaTeX","file":"latex"},"less":{"title":"Less","file":"less"},"liquid":{"title":"Liquid","file":"liquid"},"lisp":{"title":"Lisp","file":"lisp"},"livescript":{"title":"LiveScript","file":"livescript"},"lolcode":{"title":"LOLCODE","file":"lolcode"},"lua":{"title":"Lua","file":"lua"},"makefile":{"title":"Makefile","file":"makefile"},"markdown":{"title":"Markdown","file":"markdown"},"markup-templating":{"title":"Markup templating","file":"markup-templating"},"matlab":{"title":"MATLAB","file":"matlab"},"mel":{"title":"MEL","file":"mel"},"mizar":{"title":"Mizar","file":"mizar"},"monkey":{"title":"Monkey","file":"monkey"},"n4js":{"title":"N4JS","file":"n4js"},"nasm":{"title":"NASM","file":"nasm"},"nginx":{"title":"nginx","file":"nginx"},"nim":{"title":"Nim","file":"nim"},"nix":{"title":"Nix","file":"nix"},"nsis":{"title":"NSIS","file":"nsis"},"objectivec":{"title":"Objective-C","file":"objectivec"},"ocaml":{"title":"OCaml","file":"ocaml"},"opencl":{"title":"OpenCL","file":"opencl"},"oz":{"title":"Oz","file":"oz"},"parigp":{"title":"PARI/GP","file":"parigp"},"parser":{"title":"Parser","file":"parser"},"pascal":{"title":"Pascal","file":"pascal"},"perl":{"title":"Perl","file":"perl"},"php":{"title":"PHP","file":"php"},"php-extras":{"title":"PHP Extras","file":"php-extras"},"plsql":{"title":"PL/SQL","file":"plsql"},"powershell":{"title":"PowerShell","file":"powershell"},"processing":{"title":"Processing","file":"processing"},"prolog":{"title":"Prolog","file":"prolog"},"properties":{"title":".properties","file":"properties"},"protobuf":{"title":"Protocol Buffers","file":"protobuf"},"pug":{"title":"Pug","file":"pug"},"puppet":{"title":"Puppet","file":"puppet"},"pure":{"title":"Pure","file":"pure"},"python":{"title":"Python","file":"python"},"q":{"title":"Q (kdb+ database)","file":"q"},"qore":{"title":"Qore","file":"qore"},"r":{"title":"R","file":"r"},"jsx":{"title":"React JSX","file":"jsx"},"tsx":{"title":"React TSX","file":"tsx"},"renpy":{"title":"Ren'py","file":"renpy"},"reason":{"title":"Reason","file":"reason"},"rest":{"title":"reST (reStructuredText)","file":"rest"},"rip":{"title":"Rip","file":"rip"},"roboconf":{"title":"Roboconf","file":"roboconf"},"ruby":{"title":"Ruby","file":"ruby"},"rust":{"title":"Rust","file":"rust"},"sas":{"title":"SAS","file":"sas"},"sass":{"title":"Sass (Sass)","file":"sass"},"scss":{"title":"Sass (Scss)","file":"scss"},"scala":{"title":"Scala","file":"scala"},"scheme":{"title":"Scheme","file":"scheme"},"smalltalk":{"title":"Smalltalk","file":"smalltalk"},"smarty":{"title":"Smarty","file":"smarty"},"sql":{"title":"SQL","file":"sql"},"soy":{"title":"Soy (Closure Template)","file":"soy"},"stylus":{"title":"Stylus","file":"stylus"},"swift":{"title":"Swift","file":"swift"},"tap":{"title":"TAP","file":"tap"},"tcl":{"title":"Tcl","file":"tcl"},"textile":{"title":"Textile","file":"textile"},"tt2":{"title":"Template Toolkit 2","file":"tt2"},"twig":{"title":"Twig","file":"twig"},"typescript":{"title":"TypeScript","file":"typescript"},"vbnet":{"title":"VB.Net","file":"vbnet"},"velocity":{"title":"Velocity","file":"velocity"},"verilog":{"title":"Verilog","file":"verilog"},"vhdl":{"title":"VHDL","file":"vhdl"},"vim":{"title":"vim","file":"vim"},"visual-basic":{"title":"Visual Basic","file":"visual-basic"},"wasm":{"title":"WebAssembly","file":"wasm"},"wiki":{"title":"Wiki markup","file":"wiki"},"xeora":{"title":"Xeora","file":"xeora"},"xojo":{"title":"Xojo (REALbasic)","file":"xojo"},"xquery":{"title":"XQuery","file":"xquery"},"yaml":{"title":"YAML","file":"yaml"}}/*END*/;});
-/**
- * Highlights code in the Code bbcode.
- * 
- * @author     Tim Duesterhus
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Bbcode/Code
- */
-define('WoltLabSuite/Core/Bbcode/Code',[
-               'Language', 'WoltLabSuite/Core/Ui/Notification', 'WoltLabSuite/Core/Clipboard', 'WoltLabSuite/Core/Prism', 'prism/prism-meta'
-       ],
-       function(
-               Language, UiNotification, Clipboard, Prism, PrismMeta
-       )
-{
-       "use strict";
-       
-       /** @const */ var CHUNK_SIZE = 50;
-       
-       // Define idleify() for piecewiese highlighting to not block the UI thread.
-       var idleify = function (callback) {
-               return function () {
-                       var args = arguments;
-                       return new Promise(function (resolve, reject) {
-                               var body = function () {
-                                       try {
-                                               resolve(callback.apply(null, args));
-                                       }
-                                       catch (e) {
-                                               reject(e);
-                                       }
-                               };
-                               
-                               if (window.requestIdleCallback) {
-                                       window.requestIdleCallback(body, { timeout: 5000 });
-                               }
-                               else {
-                                       setTimeout(body, 0);
-                               }
-                       });
-               };
-       };
-       
-       /**
-        * @constructor
-        */
-       function Code(container) {
-               var matches;
-               
-               this.container = container;
-               this.codeContainer = elBySel('.codeBoxCode > code', this.container);
-               this.language = null;
-               for (var i = 0; i < this.codeContainer.classList.length; i++) {
-                       if ((matches = this.codeContainer.classList[i].match(/language-(.*)/))) {
-                               this.language = matches[1];
-                       }
-               }
-       }
-       Code.processAll = function () {
-               elBySelAll('.codeBox:not([data-processed])', document, function (codeBox) {
-                       elData(codeBox, 'processed', '1');
-
-                       var handle = new Code(codeBox);
-                       if (handle.language) handle.highlight();
-                       handle.createCopyButton();
-               })
-       };
-       Code.prototype = {
-               createCopyButton: function () {
-                       var header = elBySel('.codeBoxHeader', this.container);
-                       var button = elCreate('span');
-                       button.className = 'icon icon24 fa-files-o pointer jsTooltip';
-                       button.setAttribute('title', Language.get('wcf.message.bbcode.code.copy'));
-                       button.addEventListener('click', function () {
-                               Clipboard.copyElementTextToClipboard(this.codeContainer).then(function () {
-                                       UiNotification.show(Language.get('wcf.message.bbcode.code.copy.success'));
-                               });
-                       }.bind(this));
-                       
-                       header.appendChild(button);
-               },
-               highlight: function () {
-                       if (!this.language) {
-                               return Promise.reject(new Error('No language detected'));
-                       }
-                       if (!PrismMeta[this.language]) {
-                               return Promise.reject(new Error('Unknown language ' + this.language));
-                       }
-                       
-                       this.container.classList.add('highlighting');
-                       
-                       return require(['prism/components/prism-' + PrismMeta[this.language].file])
-                       .then(idleify(function () {
-                               var grammar = Prism.languages[this.language];
-                               if (!grammar) {
-                                       throw new Error('Invalid language ' + language + ' given.');
-                               }
-                               
-                               var container = elCreate('div');
-                               container.innerHTML = Prism.highlight(this.codeContainer.textContent, grammar, this.language);
-                               return container;
-                       }.bind(this)))
-                       .then(idleify(function (container) {
-                               var highlighted = Prism.wscSplitIntoLines(container);
-                               var highlightedLines = elBySelAll('[data-number]', highlighted);
-                               var originalLines = elBySelAll('.codeBoxLine > span', this.codeContainer);
-                               
-                               if (highlightedLines.length !== originalLines.length) {
-                                       throw new Error('Unreachable');
-                               }
-                               
-                               var promises = [];
-                               for (var chunkStart = 0, max = highlightedLines.length; chunkStart < max; chunkStart += CHUNK_SIZE) {
-                                       promises.push(idleify(function (chunkStart) {
-                                               var chunkEnd = Math.min(chunkStart + CHUNK_SIZE, max);
-                                               
-                                               for (var offset = chunkStart; offset < chunkEnd; offset++) {
-                                                       originalLines[offset].parentNode.replaceChild(highlightedLines[offset], originalLines[offset]);
-                                               }
-                                       })(chunkStart));
-                               }
-                               return Promise.all(promises);
-                       }.bind(this)))
-                       .then(function () {
-                               this.container.classList.remove('highlighting');
-                               this.container.classList.add('highlighted');
-                       }.bind(this))
-               }
-       }
-       
-       return Code;
-});
-
-/**
- * Generic handler for collapsible bbcode boxes.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Bbcode/Collapsible
- */
-define('WoltLabSuite/Core/Bbcode/Collapsible',[], function() {
-       "use strict";
-       
-       var _containers = elByClass('jsCollapsibleBbcode');
-       
-       /**
-        * @exports     WoltLabSuite/Core/Bbcode/Collapsible
-        */
-       return {
-               observe: function() {
-                       var container, toggleButton;
-                       while (_containers.length) {
-                               container = _containers[0];
-                               
-                               // find the matching toggle button
-                               toggleButton = null;
-                               elBySelAll('.toggleButton:not(.jsToggleButtonEnabled)', container, function (button) {
-                                       //noinspection JSReferencingMutableVariableFromClosure
-                                       if (button.closest('.jsCollapsibleBbcode') === container) {
-                                               toggleButton = button;
-                                       }
-                               });
-                               
-                               if (toggleButton) {
-                                       (function (container, toggleButton) {
-                                               var toggle = function (event) {
-                                                       if (container.classList.toggle('collapsed')) {
-                                                               toggleButton.textContent = elData(toggleButton, 'title-expand');
-                                                               
-                                                               if (event instanceof Event) {
-                                                                       // negative top value means the upper boundary is not within the viewport
-                                                                       var top = container.getBoundingClientRect().top;
-                                                                       if (top < 0) {
-                                                                               var y = window.pageYOffset + (top - 100);
-                                                                               if (y < 0) y = 0;
-                                                                               window.scrollTo(window.pageXOffset, y);
-                                                                       }
-                                                               }
-                                                       }
-                                                       else {
-                                                               toggleButton.textContent = elData(toggleButton, 'title-collapse');
-                                                       }
-                                               };
-                                               
-                                               toggleButton.classList.add('jsToggleButtonEnabled');
-                                               toggleButton.addEventListener(WCF_CLICK_EVENT, toggle);
-                                               
-                                               // expand boxes that are initially scrolled
-                                               if (container.scrollTop !== 0) {
-                                                       toggle();
-                                               }
-                                               container.addEventListener('scroll', function () {
-                                                       if (container.classList.contains('collapsed')) {
-                                                               toggle();
-                                                       }
-                                               });
-                                       })(container, toggleButton);
-                               }
-                               
-                               container.classList.remove('jsCollapsibleBbcode');
-                       }
-               }
-       };
-});
-
-/**
- * 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     WoltLabSuite/Core/Controller/Captcha
- */
-define('WoltLabSuite/Core/Controller/Captcha',['Dictionary'], function(Dictionary) {
-       "use strict";
-       
-       var _captchas = new Dictionary();
-       
-       /**
-        * @exports     WoltLabSuite/Core/Controller/Captcha
-        */
-       return {
-               /**
-                * Registers a captcha with the given identifier and callback used to get captcha data.
-                * 
-                * @param       {string}        captchaId       captcha identifier
-                * @param       {function}      callback        callback to get captcha data
-                */
-               add: function(captchaId, callback) {
-                       if (_captchas.has(captchaId)) {
-                               throw new Error("Captcha with id '" + captchaId + "' is already registered.");
-                       }
-                       
-                       if (typeof callback !== 'function') {
-                               throw new TypeError("Expected a valid callback for parameter 'callback'.");
-                       }
-                       
-                       _captchas.set(captchaId, callback);
-               },
-               
-               /**
-                * Deletes the captcha with the given identifier.
-                * 
-                * @param       {string}        captchaId       identifier of the captcha to be deleted
-                */
-               'delete': function(captchaId) {
-                       if (!_captchas.has(captchaId)) {
-                               throw new Error("Unknown captcha with id '" + captchaId + "'.");
-                       }
-                       
-                       _captchas.delete(captchaId);
-               },
-               
-               /**
-                * Returns true if a captcha with the given identifier exists.
-                * 
-                * @param       {string}        captchaId       captcha identifier
-                * @return      {boolean}
-                */
-               has: function(captchaId) {
-                       return _captchas.has(captchaId);
-               },
-               
-               /**
-                * Returns the data of the captcha with the given identifier.
-                * 
-                * @param       {string}        captchaId       captcha identifier
-                * @return      {Object}        captcha data
-                */
-               getData: function(captchaId) {
-                       if (!_captchas.has(captchaId)) {
-                               throw new Error("Unknown captcha with id '" + captchaId + "'.");
-                       }
-                       
-                       return _captchas.get(captchaId)();
-               }
-       };
-});
-
-/**
- * Clipboard API Handler.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Controller/Clipboard
- */
-define(
-       'WoltLabSuite/Core/Controller/Clipboard',[
-               'Ajax',         'Core',     'Dictionary',      'EventHandler',
-               'Language',     'List',     'ObjectMap',       'Dom/ChangeListener',
-               'Dom/Traverse', 'Dom/Util', 'Ui/Confirmation', 'Ui/SimpleDropdown',
-               'WoltLabSuite/Core/Ui/Page/Action', 'Ui/Screen'
-       ],
-       function(
-               Ajax,            Core,       Dictionary,        EventHandler,
-               Language,        List,       ObjectMap,         DomChangeListener,
-               DomTraverse,     DomUtil,    UiConfirmation,    UiSimpleDropdown,
-               UiPageAction,    UiScreen
-       )
-{
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               return {
-                       setup: function() {},
-                       reload: function() {},
-                       _initContainers: function() {},
-                       _loadMarkedItems: function() {},
-                       _markAll: function() {},
-                       _mark: function() {},
-                       _saveState: function() {},
-                       _executeAction: function() {},
-                       _executeProxyAction: function() {},
-                       _unmarkAll: function() {},
-                       _ajaxSetup: function() {},
-                       _ajaxSuccess: function() {},
-                       _rebuildMarkings: function() {},
-                       hideEditor: function() {},
-                       showEditor: function() {},
-                       unmark: function() {}
-               };
-       }
-       
-       var _containers = new Dictionary();
-       var _editors = new Dictionary();
-       var _editorDropdowns = new Dictionary();
-       var _elements = elByClass('jsClipboardContainer');
-       var _itemData = new ObjectMap();
-       var _knownCheckboxes = new List();
-       var _options = {};
-       var _reloadPageOnSuccess = new Dictionary();
-       
-       var _callbackCheckbox = null;
-       var _callbackItem = null;
-       var _callbackUnmarkAll = null;
-       
-       var _specialCheckboxSelector = '.messageCheckboxLabel > input[type="checkbox"], .message .messageClipboardCheckbox > input[type="checkbox"], .messageGroupList .columnMark > label > input[type="checkbox"]';
-       
-       /**
-        * Clipboard API
-        * 
-        * @exports     WoltLabSuite/Core/Controller/Clipboard
-        */
-       return {
-               /**
-                * Initializes the clipboard API handler.
-                * 
-                * @param       {Object}        options         initialization options
-                */
-               setup: function(options) {
-                       if (!options.pageClassName) {
-                               throw new Error("Expected a non-empty string for parameter 'pageClassName'.");
-                       }
-                       
-                       if (_callbackCheckbox === null) {
-                               _callbackCheckbox = this._mark.bind(this);
-                               _callbackItem = this._executeAction.bind(this);
-                               _callbackUnmarkAll = this._unmarkAll.bind(this);
-                               
-                               _options = Core.extend({
-                                       hasMarkedItems: false,
-                                       pageClassNames: [options.pageClassName],
-                                       pageObjectId: 0
-                               }, options);
-                               
-                               delete _options.pageClassName;
-                       }
-                       else {
-                               if (options.pageObjectId) {
-                                       throw new Error("Cannot load secondary clipboard with page object id set.");
-                               }
-                               
-                               _options.pageClassNames.push(options.pageClassName);
-                       }
-                       
-                       if (!Element.prototype.matches) {
-                               Element.prototype.matches = Element.prototype.msMatchesSelector;
-                       }
-                       
-                       this._initContainers();
-                       
-                       if (_options.hasMarkedItems && _elements.length) {
-                               this._loadMarkedItems();
-                       }
-                       
-                       DomChangeListener.add('WoltLabSuite/Core/Controller/Clipboard', this._initContainers.bind(this));
-               },
-               
-               /**
-                * Reloads the clipboard data.
-                */
-               reload: function() {
-                       if (_containers.size) {
-                               this._loadMarkedItems();
-                       }
-               },
-               
-               /**
-                * Initializes clipboard containers.
-                */
-               _initContainers: function() {
-                       for (var i = 0, length = _elements.length; i < length; i++) {
-                               var container = _elements[i];
-                               var containerId = DomUtil.identify(container);
-                               var containerData = _containers.get(containerId);
-                               
-                               if (containerData === undefined) {
-                                       var markAll = elBySel('.jsClipboardMarkAll', container);
-                                       
-                                       if (markAll !== null) {
-                                               if (markAll.matches(_specialCheckboxSelector)) {
-                                                       var label = markAll.closest('label');
-                                                       elAttr(label, 'role', 'checkbox');
-                                                       elAttr(label, 'tabindex', '0');
-                                                       elAttr(label, 'aria-checked', false);
-                                                       elAttr(label, 'aria-label', Language.get('wcf.clipboard.item.markAll'));
-                                                       
-                                                       label.addEventListener('keyup', function (event) {
-                                                               if (event.keyCode === 13 || event.keyCode === 32) {
-                                                                       checkbox.click();
-                                                               }
-                                                       });
-                                               }
-                                               
-                                               elData(markAll, 'container-id', containerId);
-                                               markAll.addEventListener(WCF_CLICK_EVENT, this._markAll.bind(this));
-                                       }
-                                       
-                                       containerData = {
-                                               checkboxes: elByClass('jsClipboardItem', container),
-                                               element: container,
-                                               markAll: markAll,
-                                               markedObjectIds: new List()
-                                       };
-                                       _containers.set(containerId, containerData);
-                               }
-                               
-                               for (var j = 0, innerLength = containerData.checkboxes.length; j < innerLength; j++) {
-                                       var checkbox = containerData.checkboxes[j];
-                                       
-                                       if (!_knownCheckboxes.has(checkbox)) {
-                                               elData(checkbox, 'container-id', containerId);
-                                               
-                                               (function(checkbox) {
-                                                       if (checkbox.matches(_specialCheckboxSelector)) {
-                                                               var label = checkbox.closest('label');
-                                                               elAttr(label, 'role', 'checkbox');
-                                                               elAttr(label, 'tabindex', '0');
-                                                               elAttr(label, 'aria-checked', false);
-                                                               elAttr(label, 'aria-label', Language.get('wcf.clipboard.item.mark'));
-                                                               
-                                                               label.addEventListener('keyup', function (event) {
-                                                                       if (event.keyCode === 13 || event.keyCode === 32) {
-                                                                               checkbox.click();
-                                                                       }
-                                                               });
-                                                       }
-                                                       
-                                                       var link = checkbox.closest('a');
-                                                       if (link === null) {
-                                                               checkbox.addEventListener(WCF_CLICK_EVENT, _callbackCheckbox);
-                                                       }
-                                                       else {
-                                                               // Firefox will always trigger the link if the checkbox is
-                                                               // inside of one. Since 2000. Thanks Firefox. 
-                                                               checkbox.addEventListener(WCF_CLICK_EVENT, function (event) {
-                                                                       event.preventDefault();
-                                                                       
-                                                                       window.setTimeout(function () {
-                                                                               checkbox.checked = !checkbox.checked;
-                                                                               
-                                                                               _callbackCheckbox(null, checkbox);
-                                                                       }, 10);
-                                                               });
-                                                       }
-                                               })(checkbox);
-                                               
-                                               _knownCheckboxes.add(checkbox);
-                                       }
-                               }
-                       }
-               },
-               
-               /**
-                * Loads marked items from clipboard.
-                */
-               _loadMarkedItems: function() {
-                       Ajax.api(this, {
-                               actionName: 'getMarkedItems',
-                               parameters: {
-                                       pageClassNames: _options.pageClassNames,
-                                       pageObjectID: _options.pageObjectId
-                               }
-                       });
-               },
-               
-               /**
-                * Marks or unmarks all visible items at once.
-                * 
-                * @param       {object}        event   event object
-                */
-               _markAll: function(event) {
-                       var checkbox = event.currentTarget;
-                       var isMarked = (checkbox.nodeName !== 'INPUT' || checkbox.checked);
-                       
-                       if (elAttr(checkbox.parentNode, 'role') === 'checkbox') {
-                               elAttr(checkbox.parentNode, 'aria-checked', isMarked);
-                       }
-                       
-                       var objectIds = [];
-                       
-                       var containerId = elData(checkbox, 'container-id');
-                       var data = _containers.get(containerId);
-                       var type = elData(data.element, 'type');
-                       
-                       for (var i = 0, length = data.checkboxes.length; i < length; i++) {
-                               var item = data.checkboxes[i];
-                               var objectId = ~~elData(item, 'object-id');
-                               
-                               if (isMarked) {
-                                       if (!item.checked) {
-                                               item.checked = true;
-                                               
-                                               data.markedObjectIds.add(objectId);
-                                               objectIds.push(objectId);
-                                       }
-                               }
-                               else {
-                                       if (item.checked) {
-                                               item.checked = false;
-                                               
-                                               data.markedObjectIds['delete'](objectId);
-                                               objectIds.push(objectId);
-                                       }
-                               }
-                               
-                               if (elAttr(item.parentNode, 'role') === 'checkbox') {
-                                       elAttr(item.parentNode, 'aria-checked', isMarked);
-                               }
-                               
-                               var clipboardObject = DomTraverse.parentByClass(checkbox, 'jsClipboardObject');
-                               if (clipboardObject !== null) {
-                                       clipboardObject.classList[(isMarked ? 'addClass' : 'removeClass')]('jsMarked');
-                               }
-                       }
-                       
-                       this._saveState(type, objectIds, isMarked);
-               },
-               
-               /**
-                * Marks or unmarks an individual item.
-                * 
-                * @param       {object}        event           event object
-                * @param       {Element=}      checkbox        checkbox element
-                */
-               _mark: function(event, checkbox) {
-                       checkbox = (event instanceof Event) ? event.currentTarget : checkbox;
-                       var objectId = ~~elData(checkbox, 'object-id');
-                       var isMarked = checkbox.checked;
-                       var containerId = elData(checkbox, 'container-id');
-                       var data = _containers.get(containerId);
-                       var type = elData(data.element, 'type');
-                       
-                       var clipboardObject = DomTraverse.parentByClass(checkbox, 'jsClipboardObject');
-                       data.markedObjectIds[(isMarked ? 'add' : 'delete')](objectId);
-                       clipboardObject.classList[(isMarked) ? 'add' : 'remove']('jsMarked');
-                       
-                       if (data.markAll !== null) {
-                               var markedAll = true;
-                               for (var i = 0, length = data.checkboxes.length; i < length; i++) {
-                                       if (!data.checkboxes[i].checked) {
-                                               markedAll = false;
-                                               
-                                               break;
-                                       }
-                               }
-                               
-                               data.markAll.checked = markedAll;
-                               
-                               if (elAttr(data.markAll.parentNode, 'role') === 'checkbox') {
-                                       elAttr(data.markAll.parentNode, 'aria-checked', isMarked);
-                               }
-                       }
-                       
-                       if (elAttr(checkbox.parentNode, 'role') === 'checkbox') {
-                               elAttr(checkbox.parentNode, 'aria-checked', checkbox.checked);
-                       }
-                       
-                       this._saveState(type, [ objectId ], isMarked);
-               },
-               
-               /**
-                * Saves the state for given item object ids.
-                * 
-                * @param       {string}        type            object type
-                * @param       {int[]}         objectIds       item object ids
-                * @param       {boolean}       isMarked        true if marked
-                */
-               _saveState: function(type, objectIds, isMarked) {
-                       Ajax.api(this, {
-                               actionName: (isMarked ? 'mark' : 'unmark'),
-                               parameters: {
-                                       pageClassNames: _options.pageClassNames,
-                                       pageObjectID: _options.pageObjectId,
-                                       objectIDs: objectIds,
-                                       objectType: type
-                               }
-                       });
-               },
-               
-               /**
-                * Executes an editor action.
-                * 
-                * @param       {object}        event           event object
-                */
-               _executeAction: function(event) {
-                       var listItem = event.currentTarget;
-                       var data = _itemData.get(listItem);
-                       
-                       if (data.url) {
-                               window.location.href = data.url;
-                               return;
-                       }
-                       
-                       var triggerEvent = function() {
-                               var type = elData(listItem, 'type');
-                               
-                               EventHandler.fire('com.woltlab.wcf.clipboard', type, {
-                                       data: data,
-                                       listItem: listItem,
-                                       responseData: null
-                               });
-                       };
-                       
-                       //noinspection JSUnresolvedVariable
-                       var confirmMessage = (typeof data.internalData.confirmMessage === 'string') ? data.internalData.confirmMessage : '';
-                       var fireEvent = true;
-                       
-                       if (typeof data.parameters === 'object' && data.parameters.actionName && data.parameters.className) {
-                               if (data.parameters.actionName === 'unmarkAll' || Array.isArray(data.parameters.objectIDs)) {
-                                       if (confirmMessage.length) {
-                                               //noinspection JSUnresolvedVariable
-                                               var template = (typeof data.internalData.template === 'string') ? data.internalData.template : '';
-                                               
-                                               UiConfirmation.show({
-                                                       confirm: (function() {
-                                                               var formData = {};
-                                                               
-                                                               if (template.length) {
-                                                                       var items = elBySelAll('input, select, textarea', UiConfirmation.getContentElement());
-                                                                       for (var i = 0, length = items.length; i < length; i++) {
-                                                                               var item = items[i];
-                                                                               var name = elAttr(item, 'name');
-                                                                               
-                                                                               switch (item.nodeName) {
-                                                                                       case 'INPUT':
-                                                                                               if ((item.type !== "checkbox" && item.type !== "radio") || item.checked) {
-                                                                                                       formData[name] = elAttr(item, 'value');
-                                                                                               }
-                                                                                               break;
-                                                                                       
-                                                                                       case 'SELECT':
-                                                                                               formData[name] = item.value;
-                                                                                               break;
-                                                                                       
-                                                                                       case 'TEXTAREA':
-                                                                                               formData[name] = item.value.trim();
-                                                                                               break;
-                                                                               }
-                                                                       }
-                                                               }
-                                                               
-                                                               //noinspection JSUnresolvedFunction
-                                                               this._executeProxyAction(listItem, data, formData);
-                                                       }).bind(this),
-                                                       message: confirmMessage,
-                                                       template: template
-                                               });
-                                       }
-                                       else {
-                                               this._executeProxyAction(listItem, data);
-                                       }
-                               }
-                       }
-                       else if (confirmMessage.length) {
-                               fireEvent = false;
-                               
-                               UiConfirmation.show({
-                                       confirm: triggerEvent,
-                                       message: confirmMessage
-                               });
-                       }
-                       
-                       if (fireEvent) {
-                               triggerEvent();
-                       }
-               },
-               
-               /**
-                * Forwards clipboard actions to an individual handler.
-                * 
-                * @param       {Element}       listItem        dropdown item element
-                * @param       {Object}        data            action data
-                * @param       {Object?}       formData        form data
-                */
-               _executeProxyAction: function(listItem, data, formData) {
-                       formData = formData || {};
-                       
-                       var objectIds = (data.parameters.actionName !== 'unmarkAll') ? data.parameters.objectIDs : [];
-                       var parameters = { data: formData };
-                       
-                       //noinspection JSUnresolvedVariable
-                       if (typeof data.internalData.parameters === 'object') {
-                               //noinspection JSUnresolvedVariable
-                               for (var key in data.internalData.parameters) {
-                                       //noinspection JSUnresolvedVariable
-                                       if (data.internalData.parameters.hasOwnProperty(key)) {
-                                               //noinspection JSUnresolvedVariable
-                                               parameters[key] = data.internalData.parameters[key];
-                                       }
-                               }
-                       }
-                       
-                       Ajax.api(this, {
-                               actionName: data.parameters.actionName,
-                               className: data.parameters.className,
-                               objectIDs: objectIds,
-                               parameters: parameters
-                       }, (function(responseData) {
-                               if (data.actionName !== 'unmarkAll') {
-                                       var type = elData(listItem, 'type');
-                                       
-                                       EventHandler.fire('com.woltlab.wcf.clipboard', type, {
-                                               data: data,
-                                               listItem: listItem,
-                                               responseData: responseData
-                                       });
-                                       
-                                       if (_reloadPageOnSuccess.has(type) && _reloadPageOnSuccess.get(type).indexOf(responseData.actionName) !== -1) {
-                                               window.location.reload();
-                                               return;
-                                       }
-                               }
-                               
-                               this._loadMarkedItems();
-                       }).bind(this));
-               },
-               
-               /**
-                * Unmarks all clipboard items for an object type.
-                * 
-                * @param       {object}        event           event object
-                */
-               _unmarkAll: function(event) {
-                       var type = elData(event.currentTarget, 'type');
-                       
-                       Ajax.api(this, {
-                               actionName: 'unmarkAll',
-                               parameters: {
-                                       objectType: type
-                               }
-                       });
-               },
-               
-               /**
-                * Sets up ajax request object.
-                * 
-                * @return      {object}        request options
-                */
-               _ajaxSetup: function() {
-                       return {
-                               data: {
-                                       className: 'wcf\\data\\clipboard\\item\\ClipboardItemAction'
-                               }
-                       };
-               },
-               
-               /**
-                * Handles successful AJAX requests.
-                * 
-                * @param       {object}        data    response data
-                */
-               _ajaxSuccess: function(data) {
-                       if (data.actionName === 'unmarkAll') {
-                               _containers.forEach((function(containerData) {
-                                       if (elData(containerData.element, 'type') === data.returnValues.objectType) {
-                                               var clipboardObjects = elByClass('jsMarked', containerData.element);
-                                               while (clipboardObjects.length) {
-                                                       clipboardObjects[0].classList.remove('jsMarked');
-                                               }
-                                               
-                                               if (containerData.markAll !== null) {
-                                                       containerData.markAll.checked = false;
-                                                       
-                                                       if (elAttr(containerData.markAll.parentNode, 'role') === 'checkbox') {
-                                                               elAttr(containerData.markAll.parentNode, 'aria-checked', false);
-                                                       }
-                                               }
-                                               for (var i = 0, length = containerData.checkboxes.length; i < length; i++) {
-                                                       containerData.checkboxes[i].checked = false;
-                                                       
-                                                       if (elAttr(containerData.checkboxes[i].parentNode, 'role') === 'checkbox') {
-                                                               elAttr(containerData.checkboxes[i].parentNode, 'aria-checked', false);
-                                                       }
-                                               }
-                                               
-                                               UiPageAction.remove('wcfClipboard-' + data.returnValues.objectType);
-                                       }
-                               }).bind(this));
-                               
-                               return;
-                       }
-                       
-                       _itemData = new ObjectMap();
-                       _reloadPageOnSuccess = new Dictionary();
-                       
-                       // rebuild markings
-                       _containers.forEach((function(containerData) {
-                               var typeName = elData(containerData.element, 'type');
-                               
-                               //noinspection JSUnresolvedVariable
-                               var objectIds = (data.returnValues.markedItems && data.returnValues.markedItems.hasOwnProperty(typeName)) ? data.returnValues.markedItems[typeName] : [];
-                               this._rebuildMarkings(containerData, objectIds);
-                       }).bind(this));
-                       
-                       var keepEditors = [], typeName;
-                       if (data.returnValues && data.returnValues.items) {
-                               for (typeName in data.returnValues.items) {
-                                       if (data.returnValues.items.hasOwnProperty(typeName)) {
-                                               keepEditors.push(typeName);
-                                       }
-                               }
-                       }
-                       
-                       // clear editors
-                       _editors.forEach(function(editor, typeName) {
-                               if (keepEditors.indexOf(typeName) === -1) {
-                                       UiPageAction.remove('wcfClipboard-' + typeName);
-                                       
-                                       _editorDropdowns.get(typeName).innerHTML = '';
-                               }
-                       });
-                       
-                       // no items
-                       if (!data.returnValues || !data.returnValues.items) {
-                               return;
-                       }
-                       
-                       // rebuild editors
-                       var actionName, created, dropdown, editor, typeData;
-                       var divider, item, itemData, itemIndex, label, unmarkAll;
-                       for (typeName in data.returnValues.items) {
-                               if (!data.returnValues.items.hasOwnProperty(typeName)) {
-                                       continue;
-                               }
-                               
-                               typeData = data.returnValues.items[typeName];
-                               //noinspection JSUnresolvedVariable
-                               _reloadPageOnSuccess.set(typeName, typeData.reloadPageOnSuccess);
-                               created = false;
-                               
-                               editor = _editors.get(typeName);
-                               dropdown = _editorDropdowns.get(typeName);
-                               if (editor === undefined) {
-                                       created = true;
-                                       
-                                       editor = elCreate('a');
-                                       editor.className = 'dropdownToggle';
-                                       editor.textContent = typeData.label;
-                                       
-                                       _editors.set(typeName, editor);
-                                       
-                                       dropdown = elCreate('ol');
-                                       dropdown.className = 'dropdownMenu';
-                                       
-                                       _editorDropdowns.set(typeName, dropdown);
-                               }
-                               else {
-                                       editor.textContent = typeData.label;
-                                       dropdown.innerHTML = '';
-                               }
-                               
-                               // create editor items
-                               for (itemIndex in typeData.items) {
-                                       if (!typeData.items.hasOwnProperty(itemIndex)) {
-                                               continue;
-                                       }
-                                       
-                                       itemData = typeData.items[itemIndex];
-                                       
-                                       item = elCreate('li');
-                                       label = elCreate('span');
-                                       label.textContent = itemData.label;
-                                       item.appendChild(label);
-                                       dropdown.appendChild(item);
-                                       
-                                       elData(item, 'type', typeName);
-                                       item.addEventListener(WCF_CLICK_EVENT, _callbackItem);
-                                       
-                                       _itemData.set(item, itemData);
-                               }
-                               
-                               divider = elCreate('li');
-                               divider.classList.add('dropdownDivider');
-                               dropdown.appendChild(divider);
-                               
-                               // add 'unmark all'
-                               unmarkAll = elCreate('li');
-                               elData(unmarkAll, 'type', typeName);
-                               label = elCreate('span');
-                               label.textContent = Language.get('wcf.clipboard.item.unmarkAll');
-                               unmarkAll.appendChild(label);
-                               unmarkAll.addEventListener(WCF_CLICK_EVENT, _callbackUnmarkAll);
-                               dropdown.appendChild(unmarkAll);
-                               
-                               if (keepEditors.indexOf(typeName) !== -1) {
-                                       actionName = 'wcfClipboard-' + typeName;
-                                       
-                                       if (UiPageAction.has(actionName)) {
-                                               UiPageAction.show(actionName);
-                                       }
-                                       else {
-                                               UiPageAction.add(actionName, editor);
-                                       }
-                               }
-                               
-                               if (created) {
-                                       editor.parentNode.classList.add('dropdown');
-                                       editor.parentNode.appendChild(dropdown);
-                                       UiSimpleDropdown.init(editor);
-                               }
-                       }
-               },
-               
-               /**
-                * Rebuilds the mark state for each item.
-                * 
-                * @param       {Object}        data            container data
-                * @param       {int[]}         objectIds       item object ids
-                */
-               _rebuildMarkings: function(data, objectIds) {
-                       var markAll = true;
-                       
-                       for (var i = 0, length = data.checkboxes.length; i < length; i++) {
-                               var checkbox = data.checkboxes[i];
-                               var clipboardObject = DomTraverse.parentByClass(checkbox, 'jsClipboardObject');
-                               
-                               var isMarked = (objectIds.indexOf(~~elData(checkbox, 'object-id')) !== -1);
-                               if (!isMarked) markAll = false;
-                               
-                               checkbox.checked = isMarked;
-                               clipboardObject.classList[(isMarked ? 'add' : 'remove')]('jsMarked');
-                               
-                               if (elAttr(checkbox.parentNode, 'role') === 'checkbox') {
-                                       elAttr(checkbox.parentNode, 'aria-checked', isMarked);
-                               }
-                       }
-                       
-                       if (data.markAll !== null) {
-                               data.markAll.checked = markAll;
-                               
-                               if (elAttr(data.markAll.parentNode, 'role') === 'checkbox') {
-                                       elAttr(data.markAll.parentNode, 'aria-checked', markAll);
-                               }
-                               
-                               var parent = data.markAll;
-                               while (parent = parent.parentNode) {
-                                       if (parent instanceof Element && parent.classList.contains('columnMark')) {
-                                               parent = parent.parentNode;
-                                               break;
-                                       }
-                               }
-                               
-                               if (parent) {
-                                       parent.classList[(markAll ? 'add' : 'remove')]('jsMarked');
-                               }
-                       }
-               },
-               
-               /**
-                * Hides the clipboard editor for the given object type.
-                * 
-                * @param       {string}        objectType
-                */
-               hideEditor: function(objectType) {
-                       UiPageAction.remove('wcfClipboard-' + objectType);
-                       
-                       UiScreen.pageOverlayOpen();
-               },
-               
-               /**
-                * Shows the clipboard editor.
-                */
-               showEditor: function() {
-                       this._loadMarkedItems();
-                       
-                       UiScreen.pageOverlayClose();
-               },
-               
-               /**
-                * Unmarks the objects with given clipboard object type and ids.
-                * 
-                * @param       {string}        objectType
-                * @param       {int[]}         objectIds
-                */
-               unmark: function(objectType, objectIds) {
-                       this._saveState(objectType, objectIds, false);
-               }
-       };
-});
-
-/**
- * Provides helper functions for Exif metadata handling.
- *
- * @author     Maximilian Mader
- * @copyright  2001-2018 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Image/ExifUtil
- */
-define('WoltLabSuite/Core/Image/ExifUtil',[], function() {
-       "use strict";
-       
-       var _tagNames = {
-               'SOI':   0xD8, // Start of image
-               'APP0':  0xE0, // JFIF tag
-               'APP1':  0xE1, // EXIF / XMP
-               'APP2':  0xE2, // General purpose tag
-               'APP3':  0xE3, // General purpose tag
-               'APP4':  0xE4, // General purpose tag
-               'APP5':  0xE5, // General purpose tag
-               'APP6':  0xE6, // General purpose tag
-               'APP7':  0xE7, // General purpose tag
-               'APP8':  0xE8, // General purpose tag
-               'APP9':  0xE9, // General purpose tag
-               'APP10': 0xEA, // General purpose tag
-               'APP11': 0xEB, // General purpose tag
-               'APP12': 0xEC, // General purpose tag
-               'APP13': 0xED, // General purpose tag
-               'APP14': 0xEE, // Often used to store copyright information
-               'COM':   0xFE, // Comments
-       };
-       
-       // Known sequence signatures
-       var _signatureEXIF = 'Exif';
-       var _signatureXMP  = 'http://ns.adobe.com/xap/1.0/';
-       
-       return {
-               /**
-                * Extracts the EXIF / XMP sections of a JPEG blob.
-                *
-                * @param       blob    {Blob}                                  JPEG blob
-                * @returns             {Promise<Uint8Array | TypeError>}       Promise resolving with the EXIF / XMP sections
-                */
-               getExifBytesFromJpeg: function (blob) {
-                       return new Promise(function (resolve, reject) {
-                               if (!(blob instanceof Blob) && !(blob instanceof File)) {
-                                       return reject(new TypeError('The argument must be a Blob or a File'));
-                               }
-                               
-                               var reader = new FileReader();
-                               
-                               reader.addEventListener('error', function () {
-                                       reader.abort();
-                                       reject(reader.error);
-                               });
-                               
-                               reader.addEventListener('load', function() {
-                                       var buffer = reader.result;
-                                       var bytes = new Uint8Array(buffer);
-                                       var exif = new Uint8Array();
-                                       
-                                       if (bytes[0] !== 0xFF && bytes[1] !== _tagNames.SOI) {
-                                               return reject(new Error('Not a JPEG'));
-                                       }
-                                       
-                                       for (var i = 2; i < bytes.length;) {
-                                               // each sequence starts with 0xFF
-                                               if (bytes[i] !== 0xFF) break;
-                                               
-                                               var length = 2 + ((bytes[i + 2] << 8) | bytes[i + 3]);
-                                               
-                                               // Check if the next byte indicates an EXIF sequence
-                                               if (bytes[i + 1] === _tagNames.APP1) {
-                                                       var signature = '';
-                                                       for (var j = i + 4; bytes[j] !== 0 && j < bytes.length; j++) {
-                                                               signature += String.fromCharCode(bytes[j]);
-                                                       }
-                                                       
-                                                       // Only copy Exif and XMP data
-                                                       if (signature === _signatureEXIF || signature === _signatureXMP) {
-                                                               // append the found EXIF sequence, usually only a single EXIF (APP1) sequence should be defined
-                                                               var sequence = Array.prototype.slice.call(bytes, i, length + i); // IE11 does not have slice in the Uint8Array prototype
-                                                               var concat = new Uint8Array(exif.length + sequence.length);
-                                                               concat.set(exif);
-                                                               concat.set(sequence, exif.length);
-                                                               exif = concat;
-                                                       }
-                                               }
-                                               
-                                               i += length
-                                       }
-                                       
-                                       // No EXIF data found
-                                       resolve(exif);
-                               });
-                               
-                               reader.readAsArrayBuffer(blob);
-                       });
-               },
-               
-               /**
-                * Removes all EXIF and XMP sections of a JPEG blob.
-                *
-                * @param       blob    {Blob}                          JPEG blob
-                * @returns             {Promise<Blob | TypeError>}     Promise resolving with the altered JPEG blob
-                */
-               removeExifData: function (blob) {
-                       return new Promise(function (resolve, reject) {
-                               if (!(blob instanceof Blob) && !(blob instanceof File)) {
-                                       return reject(new TypeError('The argument must be a Blob or a File'));
-                               }
-                               
-                               var reader = new FileReader();
-                               
-                               reader.addEventListener('error', function () {
-                                       reader.abort();
-                                       reject(reader.error);
-                               });
-                               
-                               reader.addEventListener('load', function () {
-                                       var buffer = reader.result;
-                                       var bytes = new Uint8Array(buffer);
-                                       
-                                       if (bytes[0] !== 0xFF && bytes[1] !== _tagNames.SOI) {
-                                               return reject(new Error('Not a JPEG'));
-                                       }
-                                       
-                                       for (var i = 2; i < bytes.length;) {
-                                               // each sequence starts with 0xFF
-                                               if (bytes[i] !== 0xFF) break;
-                                               
-                                               var length = 2 + ((bytes[i + 2] << 8) | bytes[i + 3]);
-                                               
-                                               // Check if the next byte indicates an EXIF sequence
-                                               if (bytes[i + 1] === _tagNames.APP1) {
-                                                       var signature = '';
-                                                       for (var j = i + 4; bytes[j] !== 0 && j < bytes.length; j++) {
-                                                               signature += String.fromCharCode(bytes[j]);
-                                                       }
-                                                       
-                                                       // Only remove Exif and XMP data
-                                                       if (signature === _signatureEXIF || signature === _signatureXMP) {
-                                                               var start = Array.prototype.slice.call(bytes, 0, i);
-                                                               var end = Array.prototype.slice.call(bytes, i + length);
-                                                               bytes = new Uint8Array(start.length + end.length);
-                                                               bytes.set(start, 0);
-                                                               bytes.set(end, start.length);
-                                                       }
-                                               }
-                                               else {
-                                                       i += length;
-                                               }
-                                       }
-                                       
-                                       resolve(new Blob([bytes], {type: blob.type}));
-                               });
-                               
-                               reader.readAsArrayBuffer(blob);
-                       });
-               },
-               
-               /**
-                * Overrides the APP1 (EXIF / XMP) sections of a JPEG blob with the given data.
-                *
-                * @param       blob    {Blob}                  JPEG blob
-                * @param       exif    {Uint8Array}            APP1 sections
-                * @returns             {Promise<Blob | never>} Promise resolving with the altered JPEG blob
-                */
-               setExifData: function (blob, exif) {
-                       return this.removeExifData(blob).then(function (blob) {
-                               return new Promise(function (resolve) {
-                                       var reader = new FileReader();
-                                       
-                                       reader.addEventListener('error', function () {
-                                               reader.abort();
-                                               reject(reader.error);
-                                       });
-                                       
-                                       reader.addEventListener('load', function () {
-                                               var buffer = reader.result;
-                                               var bytes = new Uint8Array(buffer);
-                                               var offset = 2;
-                                               
-                                               // check if the second tag is the JFIF tag
-                                               if (bytes[2] === 0xFF && bytes[3] === _tagNames.APP0) {
-                                                       offset += 2 + ((bytes[4] << 8) | bytes[5]);
-                                               }
-                                               
-                                               var start = Array.prototype.slice.call(bytes, 0, offset);
-                                               var end = Array.prototype.slice.call(bytes, offset);
-                                               
-                                               bytes = new Uint8Array(start.length + exif.length + end.length);
-                                               bytes.set(start);
-                                               bytes.set(exif, offset);
-                                               bytes.set(end, offset + exif.length);
-                                               
-                                               resolve(new Blob([bytes], {type: blob.type}));
-                                       });
-                                       
-                                       reader.readAsArrayBuffer(blob);
-                               });
-                       });
-               }
-       };
-});
-
-/**
- * Provides helper functions for Image metadata 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/Image/ImageUtil
- */
-define('WoltLabSuite/Core/Image/ImageUtil',[], function() {
-       "use strict";
-       
-       return {
-               /**
-                * Returns whether the given canvas contains transparent pixels.
-                *
-                * @param       image   {Canvas}  Canvas to check
-                * @returns             {bool}
-                */
-               containsTransparentPixels: function (canvas) {
-                       var imageData = canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height);
-                       
-                       for (var i = 3, max = imageData.data.length; i < max; i += 4) {
-                               if (imageData.data[i] !== 255) return true;
-                       }
-                       
-                       return false;
-               }
-       };
-});
-
-/* (The MIT License)
-
-Copyright (C) 2014-2017 by Vitaly Puzrin
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE. */
-/* pica 5.0.0 nodeca/pica */(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define('Pica',[],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.pica = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
-// Collection of math functions
-//
-// 1. Combine components together
-// 2. Has async init to load wasm modules
-//
-               'use strict';
-               
-               var inherits = require('inherits');
-               
-               var Multimath = require('multimath');
-               
-               var mm_unsharp_mask = require('multimath/lib/unsharp_mask');
-               
-               var mm_resize = require('./mm_resize');
-               
-               function MathLib(requested_features) {
-                       var __requested_features = requested_features || [];
-                       
-                       var features = {
-                               js: __requested_features.indexOf('js') >= 0,
-                               wasm: __requested_features.indexOf('wasm') >= 0
-                       };
-                       Multimath.call(this, features);
-                       this.features = {
-                               js: features.js,
-                               wasm: features.wasm && this.has_wasm
-                       };
-                       this.use(mm_unsharp_mask);
-                       this.use(mm_resize);
-               }
-               
-               inherits(MathLib, Multimath);
-               
-               MathLib.prototype.resizeAndUnsharp = function resizeAndUnsharp(options, cache) {
-                       var result = this.resize(options, cache);
-                       
-                       if (options.unsharpAmount) {
-                               this.unsharp_mask(result, options.toWidth, options.toHeight, options.unsharpAmount, options.unsharpRadius, options.unsharpThreshold);
-                       }
-                       
-                       return result;
-               };
-               
-               module.exports = MathLib;
-               
-       },{"./mm_resize":4,"inherits":15,"multimath":16,"multimath/lib/unsharp_mask":19}],2:[function(require,module,exports){
-// Resize convolvers, pure JS implementation
-//
-               'use strict'; // Precision of fixed FP values
-//var FIXED_FRAC_BITS = 14;
-               
-               function clampTo8(i) {
-                       return i < 0 ? 0 : i > 255 ? 255 : i;
-               } // Convolve image in horizontal directions and transpose output. In theory,
-// transpose allow:
-//
-// - use the same convolver for both passes (this fails due different
-//   types of input array and temporary buffer)
-// - making vertical pass by horisonltal lines inprove CPU cache use.
-//
-// But in real life this doesn't work :)
-//
-               
-               
-               function convolveHorizontally(src, dest, srcW, srcH, destW, filters) {
-                       var r, g, b, a;
-                       var filterPtr, filterShift, filterSize;
-                       var srcPtr, srcY, destX, filterVal;
-                       var srcOffset = 0,
-                               destOffset = 0; // For each row
-                       
-                       for (srcY = 0; srcY < srcH; srcY++) {
-                               filterPtr = 0; // Apply precomputed filters to each destination row point
-                               
-                               for (destX = 0; destX < destW; destX++) {
-                                       // Get the filter that determines the current output pixel.
-                                       filterShift = filters[filterPtr++];
-                                       filterSize = filters[filterPtr++];
-                                       srcPtr = srcOffset + filterShift * 4 | 0;
-                                       r = g = b = a = 0; // Apply the filter to the row to get the destination pixel r, g, b, a
-                                       
-                                       for (; filterSize > 0; filterSize--) {
-                                               filterVal = filters[filterPtr++]; // Use reverse order to workaround deopts in old v8 (node v.10)
-                                               // Big thanks to @mraleph (Vyacheslav Egorov) for the tip.
-                                               
-                                               a = a + filterVal * src[srcPtr + 3] | 0;
-                                               b = b + filterVal * src[srcPtr + 2] | 0;
-                                               g = g + filterVal * src[srcPtr + 1] | 0;
-                                               r = r + filterVal * src[srcPtr] | 0;
-                                               srcPtr = srcPtr + 4 | 0;
-                                       } // Bring this value back in range. All of the filter scaling factors
-                                       // are in fixed point with FIXED_FRAC_BITS bits of fractional part.
-                                       //
-                                       // (!) Add 1/2 of value before clamping to get proper rounding. In other
-                                       // case brightness loss will be noticeable if you resize image with white
-                                       // border and place it on white background.
-                                       //
-                                       
-                                       
-                                       dest[destOffset + 3] = clampTo8(a + (1 << 13) >> 14
-                                               /*FIXED_FRAC_BITS*/
-                                       );
-                                       dest[destOffset + 2] = clampTo8(b + (1 << 13) >> 14
-                                               /*FIXED_FRAC_BITS*/
-                                       );
-                                       dest[destOffset + 1] = clampTo8(g + (1 << 13) >> 14
-                                               /*FIXED_FRAC_BITS*/
-                                       );
-                                       dest[destOffset] = clampTo8(r + (1 << 13) >> 14
-                                               /*FIXED_FRAC_BITS*/
-                                       );
-                                       destOffset = destOffset + srcH * 4 | 0;
-                               }
-                               
-                               destOffset = (srcY + 1) * 4 | 0;
-                               srcOffset = (srcY + 1) * srcW * 4 | 0;
-                       }
-               } // Technically, convolvers are the same. But input array and temporary
-// buffer can be of different type (especially, in old browsers). So,
-// keep code in separate functions to avoid deoptimizations & speed loss.
-               
-               
-               function convolveVertically(src, dest, srcW, srcH, destW, filters) {
-                       var r, g, b, a;
-                       var filterPtr, filterShift, filterSize;
-                       var srcPtr, srcY, destX, filterVal;
-                       var srcOffset = 0,
-                               destOffset = 0; // For each row
-                       
-                       for (srcY = 0; srcY < srcH; srcY++) {
-                               filterPtr = 0; // Apply precomputed filters to each destination row point
-                               
-                               for (destX = 0; destX < destW; destX++) {
-                                       // Get the filter that determines the current output pixel.
-                                       filterShift = filters[filterPtr++];
-                                       filterSize = filters[filterPtr++];
-                                       srcPtr = srcOffset + filterShift * 4 | 0;
-                                       r = g = b = a = 0; // Apply the filter to the row to get the destination pixel r, g, b, a
-                                       
-                                       for (; filterSize > 0; filterSize--) {
-                                               filterVal = filters[filterPtr++]; // Use reverse order to workaround deopts in old v8 (node v.10)
-                                               // Big thanks to @mraleph (Vyacheslav Egorov) for the tip.
-                                               
-                                               a = a + filterVal * src[srcPtr + 3] | 0;
-                                               b = b + filterVal * src[srcPtr + 2] | 0;
-                                               g = g + filterVal * src[srcPtr + 1] | 0;
-                                               r = r + filterVal * src[srcPtr] | 0;
-                                               srcPtr = srcPtr + 4 | 0;
-                                       } // Bring this value back in range. All of the filter scaling factors
-                                       // are in fixed point with FIXED_FRAC_BITS bits of fractional part.
-                                       //
-                                       // (!) Add 1/2 of value before clamping to get proper rounding. In other
-                                       // case brightness loss will be noticeable if you resize image with white
-                                       // border and place it on white background.
-                                       //
-                                       
-                                       
-                                       dest[destOffset + 3] = clampTo8(a + (1 << 13) >> 14
-                                               /*FIXED_FRAC_BITS*/
-                                       );
-                                       dest[destOffset + 2] = clampTo8(b + (1 << 13) >> 14
-                                               /*FIXED_FRAC_BITS*/
-                                       );
-                                       dest[destOffset + 1] = clampTo8(g + (1 << 13) >> 14
-                                               /*FIXED_FRAC_BITS*/
-                                       );
-                                       dest[destOffset] = clampTo8(r + (1 << 13) >> 14
-                                               /*FIXED_FRAC_BITS*/
-                                       );
-                                       destOffset = destOffset + srcH * 4 | 0;
-                               }
-                               
-                               destOffset = (srcY + 1) * 4 | 0;
-                               srcOffset = (srcY + 1) * srcW * 4 | 0;
-                       }
-               }
-               
-               module.exports = {
-                       convolveHorizontally: convolveHorizontally,
-                       convolveVertically: convolveVertically
-               };
-               
-       },{}],3:[function(require,module,exports){
-// This is autogenerated file from math.wasm, don't edit.
-//
-               'use strict';
-               /* eslint-disable max-len */
-               
-               module.exports = 'AGFzbQEAAAABFAJgBn9/f39/fwBgB39/f39/f38AAg8BA2VudgZtZW1vcnkCAAEDAwIAAQQEAXAAAAcZAghjb252b2x2ZQAACmNvbnZvbHZlSFYAAQkBAArmAwLBAwEQfwJAIANFDQAgBEUNACAFQQRqIRVBACEMQQAhDQNAIA0hDkEAIRFBACEHA0AgB0ECaiESAn8gBSAHQQF0IgdqIgZBAmouAQAiEwRAQQAhCEEAIBNrIRQgFSAHaiEPIAAgDCAGLgEAakECdGohEEEAIQlBACEKQQAhCwNAIBAoAgAiB0EYdiAPLgEAIgZsIAtqIQsgB0H/AXEgBmwgCGohCCAHQRB2Qf8BcSAGbCAKaiEKIAdBCHZB/wFxIAZsIAlqIQkgD0ECaiEPIBBBBGohECAUQQFqIhQNAAsgEiATagwBC0EAIQtBACEKQQAhCUEAIQggEgshByABIA5BAnRqIApBgMAAakEOdSIGQf8BIAZB/wFIG0EQdEGAgPwHcUEAIAZBAEobIAtBgMAAakEOdSIGQf8BIAZB/wFIG0EYdEEAIAZBAEobciAJQYDAAGpBDnUiBkH/ASAGQf8BSBtBCHRBgP4DcUEAIAZBAEobciAIQYDAAGpBDnUiBkH/ASAGQf8BSBtB/wFxQQAgBkEAShtyNgIAIA4gA2ohDiARQQFqIhEgBEcNAAsgDCACaiEMIA1BAWoiDSADRw0ACwsLIQACQEEAIAIgAyAEIAUgABAAIAJBACAEIAUgBiABEAALCw==';
-               
-       },{}],4:[function(require,module,exports){
-               'use strict';
-               
-               module.exports = {
-                       name: 'resize',
-                       fn: require('./resize'),
-                       wasm_fn: require('./resize_wasm'),
-                       wasm_src: require('./convolve_wasm_base64')
-               };
-               
-       },{"./convolve_wasm_base64":3,"./resize":5,"./resize_wasm":8}],5:[function(require,module,exports){
-               'use strict';
-               
-               var createFilters = require('./resize_filter_gen');
-               
-               var convolveHorizontally = require('./convolve').convolveHorizontally;
-               
-               var convolveVertically = require('./convolve').convolveVertically;
-               
-               function resetAlpha(dst, width, height) {
-                       var ptr = 3,
-                               len = width * height * 4 | 0;
-                       
-                       while (ptr < len) {
-                               dst[ptr] = 0xFF;
-                               ptr = ptr + 4 | 0;
-                       }
-               }
-               
-               module.exports = function resize(options) {
-                       var src = options.src;
-                       var srcW = options.width;
-                       var srcH = options.height;
-                       var destW = options.toWidth;
-                       var destH = options.toHeight;
-                       var scaleX = options.scaleX || options.toWidth / options.width;
-                       var scaleY = options.scaleY || options.toHeight / options.height;
-                       var offsetX = options.offsetX || 0;
-                       var offsetY = options.offsetY || 0;
-                       var dest = options.dest || new Uint8Array(destW * destH * 4);
-                       var quality = typeof options.quality === 'undefined' ? 3 : options.quality;
-                       var alpha = options.alpha || false;
-                       var filtersX = createFilters(quality, srcW, destW, scaleX, offsetX),
-                               filtersY = createFilters(quality, srcH, destH, scaleY, offsetY);
-                       var tmp = new Uint8Array(destW * srcH * 4); // To use single function we need src & tmp of the same type.
-                       // But src can be CanvasPixelArray, and tmp - Uint8Array. So, keep
-                       // vertical and horizontal passes separately to avoid deoptimization.
-                       
-                       convolveHorizontally(src, tmp, srcW, srcH, destW, filtersX);
-                       convolveVertically(tmp, dest, srcH, destW, destH, filtersY); // That's faster than doing checks in convolver.
-                       // !!! Note, canvas data is not premultipled. We don't need other
-                       // alpha corrections.
-                       
-                       if (!alpha) resetAlpha(dest, destW, destH);
-                       return dest;
-               };
-               
-       },{"./convolve":2,"./resize_filter_gen":6}],6:[function(require,module,exports){
-// Calculate convolution filters for each destination point,
-// and pack data to Int16Array:
-//
-// [ shift, length, data..., shift2, length2, data..., ... ]
-//
-// - shift - offset in src image
-// - length - filter length (in src points)
-// - data - filter values sequence
-//
-               'use strict';
-               
-               var FILTER_INFO = require('./resize_filter_info'); // Precision of fixed FP values
-               
-               
-               var FIXED_FRAC_BITS = 14;
-               
-               function toFixedPoint(num) {
-                       return Math.round(num * ((1 << FIXED_FRAC_BITS) - 1));
-               }
-               
-               module.exports = function resizeFilterGen(quality, srcSize, destSize, scale, offset) {
-                       var filterFunction = FILTER_INFO[quality].filter;
-                       var scaleInverted = 1.0 / scale;
-                       var scaleClamped = Math.min(1.0, scale); // For upscale
-                       // Filter window (averaging interval), scaled to src image
-                       
-                       var srcWindow = FILTER_INFO[quality].win / scaleClamped;
-                       var destPixel, srcPixel, srcFirst, srcLast, filterElementSize, floatFilter, fxpFilter, total, pxl, idx, floatVal, filterTotal, filterVal;
-                       var leftNotEmpty, rightNotEmpty, filterShift, filterSize;
-                       var maxFilterElementSize = Math.floor((srcWindow + 1) * 2);
-                       var packedFilter = new Int16Array((maxFilterElementSize + 2) * destSize);
-                       var packedFilterPtr = 0;
-                       var slowCopy = !packedFilter.subarray || !packedFilter.set; // For each destination pixel calculate source range and built filter values
-                       
-                       for (destPixel = 0; destPixel < destSize; destPixel++) {
-                               // Scaling should be done relative to central pixel point
-                               srcPixel = (destPixel + 0.5) * scaleInverted + offset;
-                               srcFirst = Math.max(0, Math.floor(srcPixel - srcWindow));
-                               srcLast = Math.min(srcSize - 1, Math.ceil(srcPixel + srcWindow));
-                               filterElementSize = srcLast - srcFirst + 1;
-                               floatFilter = new Float32Array(filterElementSize);
-                               fxpFilter = new Int16Array(filterElementSize);
-                               total = 0.0; // Fill filter values for calculated range
-                               
-                               for (pxl = srcFirst, idx = 0; pxl <= srcLast; pxl++, idx++) {
-                                       floatVal = filterFunction((pxl + 0.5 - srcPixel) * scaleClamped);
-                                       total += floatVal;
-                                       floatFilter[idx] = floatVal;
-                               } // Normalize filter, convert to fixed point and accumulate conversion error
-                               
-                               
-                               filterTotal = 0;
-                               
-                               for (idx = 0; idx < floatFilter.length; idx++) {
-                                       filterVal = floatFilter[idx] / total;
-                                       filterTotal += filterVal;
-                                       fxpFilter[idx] = toFixedPoint(filterVal);
-                               } // Compensate normalization error, to minimize brightness drift
-                               
-                               
-                               fxpFilter[destSize >> 1] += toFixedPoint(1.0 - filterTotal); //
-                               // Now pack filter to useable form
-                               //
-                               // 1. Trim heading and tailing zero values, and compensate shitf/length
-                               // 2. Put all to single array in this format:
-                               //
-                               //    [ pos shift, data length, value1, value2, value3, ... ]
-                               //
-                               
-                               leftNotEmpty = 0;
-                               
-                               while (leftNotEmpty < fxpFilter.length && fxpFilter[leftNotEmpty] === 0) {
-                                       leftNotEmpty++;
-                               }
-                               
-                               if (leftNotEmpty < fxpFilter.length) {
-                                       rightNotEmpty = fxpFilter.length - 1;
-                                       
-                                       while (rightNotEmpty > 0 && fxpFilter[rightNotEmpty] === 0) {
-                                               rightNotEmpty--;
-                                       }
-                                       
-                                       filterShift = srcFirst + leftNotEmpty;
-                                       filterSize = rightNotEmpty - leftNotEmpty + 1;
-                                       packedFilter[packedFilterPtr++] = filterShift; // shift
-                                       
-                                       packedFilter[packedFilterPtr++] = filterSize; // size
-                                       
-                                       if (!slowCopy) {
-                                               packedFilter.set(fxpFilter.subarray(leftNotEmpty, rightNotEmpty + 1), packedFilterPtr);
-                                               packedFilterPtr += filterSize;
-                                       } else {
-                                               // fallback for old IE < 11, without subarray/set methods
-                                               for (idx = leftNotEmpty; idx <= rightNotEmpty; idx++) {
-                                                       packedFilter[packedFilterPtr++] = fxpFilter[idx];
-                                               }
-                                       }
-                               } else {
-                                       // zero data, write header only
-                                       packedFilter[packedFilterPtr++] = 0; // shift
-                                       
-                                       packedFilter[packedFilterPtr++] = 0; // size
-                               }
-                       }
-                       
-                       return packedFilter;
-               };
-               
-       },{"./resize_filter_info":7}],7:[function(require,module,exports){
-// Filter definitions to build tables for
-// resizing convolvers.
-//
-// Presets for quality 0..3. Filter functions + window size
-//
-               'use strict';
-               
-               module.exports = [{
-                       // Nearest neibor (Box)
-                       win: 0.5,
-                       filter: function filter(x) {
-                               return x >= -0.5 && x < 0.5 ? 1.0 : 0.0;
-                       }
-               }, {
-                       // Hamming
-                       win: 1.0,
-                       filter: function filter(x) {
-                               if (x <= -1.0 || x >= 1.0) {
-                                       return 0.0;
-                               }
-                               
-                               if (x > -1.19209290E-07 && x < 1.19209290E-07) {
-                                       return 1.0;
-                               }
-                               
-                               var xpi = x * Math.PI;
-                               return Math.sin(xpi) / xpi * (0.54 + 0.46 * Math.cos(xpi / 1.0));
-                       }
-               }, {
-                       // Lanczos, win = 2
-                       win: 2.0,
-                       filter: function filter(x) {
-                               if (x <= -2.0 || x >= 2.0) {
-                                       return 0.0;
-                               }
-                               
-                               if (x > -1.19209290E-07 && x < 1.19209290E-07) {
-                                       return 1.0;
-                               }
-                               
-                               var xpi = x * Math.PI;
-                               return Math.sin(xpi) / xpi * Math.sin(xpi / 2.0) / (xpi / 2.0);
-                       }
-               }, {
-                       // Lanczos, win = 3
-                       win: 3.0,
-                       filter: function filter(x) {
-                               if (x <= -3.0 || x >= 3.0) {
-                                       return 0.0;
-                               }
-                               
-                               if (x > -1.19209290E-07 && x < 1.19209290E-07) {
-                                       return 1.0;
-                               }
-                               
-                               var xpi = x * Math.PI;
-                               return Math.sin(xpi) / xpi * Math.sin(xpi / 3.0) / (xpi / 3.0);
-                       }
-               }];
-               
-       },{}],8:[function(require,module,exports){
-               'use strict';
-               
-               var createFilters = require('./resize_filter_gen');
-               
-               function resetAlpha(dst, width, height) {
-                       var ptr = 3,
-                               len = width * height * 4 | 0;
-                       
-                       while (ptr < len) {
-                               dst[ptr] = 0xFF;
-                               ptr = ptr + 4 | 0;
-                       }
-               }
-               
-               function asUint8Array(src) {
-                       return new Uint8Array(src.buffer, 0, src.byteLength);
-               }
-               
-               var IS_LE = true; // should not crash everything on module load in old browsers
-               
-               try {
-                       IS_LE = new Uint32Array(new Uint8Array([1, 0, 0, 0]).buffer)[0] === 1;
-               } catch (__) {}
-               
-               function copyInt16asLE(src, target, target_offset) {
-                       if (IS_LE) {
-                               target.set(asUint8Array(src), target_offset);
-                               return;
-                       }
-                       
-                       for (var ptr = target_offset, i = 0; i < src.length; i++) {
-                               var data = src[i];
-                               target[ptr++] = data & 0xFF;
-                               target[ptr++] = data >> 8 & 0xFF;
-                       }
-               }
-               
-               module.exports = function resize_wasm(options) {
-                       var src = options.src;
-                       var srcW = options.width;
-                       var srcH = options.height;
-                       var destW = options.toWidth;
-                       var destH = options.toHeight;
-                       var scaleX = options.scaleX || options.toWidth / options.width;
-                       var scaleY = options.scaleY || options.toHeight / options.height;
-                       var offsetX = options.offsetX || 0.0;
-                       var offsetY = options.offsetY || 0.0;
-                       var dest = options.dest || new Uint8Array(destW * destH * 4);
-                       var quality = typeof options.quality === 'undefined' ? 3 : options.quality;
-                       var alpha = options.alpha || false;
-                       var filtersX = createFilters(quality, srcW, destW, scaleX, offsetX),
-                               filtersY = createFilters(quality, srcH, destH, scaleY, offsetY); // destination is 0 too.
-                       
-                       var src_offset = 0; // buffer between convolve passes
-                       
-                       var tmp_offset = this.__align(src_offset + Math.max(src.byteLength, dest.byteLength));
-                       
-                       var filtersX_offset = this.__align(tmp_offset + srcH * destW * 4);
-                       
-                       var filtersY_offset = this.__align(filtersX_offset + filtersX.byteLength);
-                       
-                       var alloc_bytes = filtersY_offset + filtersY.byteLength;
-                       
-                       var instance = this.__instance('resize', alloc_bytes); //
-                       // Fill memory block with data to process
-                       //
-                       
-                       
-                       var mem = new Uint8Array(this.__memory.buffer);
-                       var mem32 = new Uint32Array(this.__memory.buffer); // 32-bit copy is much faster in chrome
-                       
-                       var src32 = new Uint32Array(src.buffer);
-                       mem32.set(src32); // We should guarantee LE bytes order. Filters are not big, so
-                       // speed difference is not significant vs direct .set()
-                       
-                       copyInt16asLE(filtersX, mem, filtersX_offset);
-                       copyInt16asLE(filtersY, mem, filtersY_offset); //
-                       // Now call webassembly method
-                       // emsdk does method names with '_'
-                       
-                       var fn = instance.exports.convolveHV || instance.exports._convolveHV;
-                       fn(filtersX_offset, filtersY_offset, tmp_offset, srcW, srcH, destW, destH); //
-                       // Copy data back to typed array
-                       //
-                       // 32-bit copy is much faster in chrome
-                       
-                       var dest32 = new Uint32Array(dest.buffer);
-                       dest32.set(new Uint32Array(this.__memory.buffer, 0, destH * destW)); // That's faster than doing checks in convolver.
-                       // !!! Note, canvas data is not premultipled. We don't need other
-                       // alpha corrections.
-                       
-                       if (!alpha) resetAlpha(dest, destW, destH);
-                       return dest;
-               };
-               
-       },{"./resize_filter_gen":6}],9:[function(require,module,exports){
-               'use strict';
-               
-               var GC_INTERVAL = 100;
-               
-               function Pool(create, idle) {
-                       this.create = create;
-                       this.available = [];
-                       this.acquired = {};
-                       this.lastId = 1;
-                       this.timeoutId = 0;
-                       this.idle = idle || 2000;
-               }
-               
-               Pool.prototype.acquire = function () {
-                       var _this = this;
-                       
-                       var resource;
-                       
-                       if (this.available.length !== 0) {
-                               resource = this.available.pop();
-                       } else {
-                               resource = this.create();
-                               resource.id = this.lastId++;
-                               
-                               resource.release = function () {
-                                       return _this.release(resource);
-                               };
-                       }
-                       
-                       this.acquired[resource.id] = resource;
-                       return resource;
-               };
-               
-               Pool.prototype.release = function (resource) {
-                       var _this2 = this;
-                       
-                       delete this.acquired[resource.id];
-                       resource.lastUsed = Date.now();
-                       this.available.push(resource);
-                       
-                       if (this.timeoutId === 0) {
-                               this.timeoutId = setTimeout(function () {
-                                       return _this2.gc();
-                               }, GC_INTERVAL);
-                       }
-               };
-               
-               Pool.prototype.gc = function () {
-                       var _this3 = this;
-                       
-                       var now = Date.now();
-                       this.available = this.available.filter(function (resource) {
-                               if (now - resource.lastUsed > _this3.idle) {
-                                       resource.destroy();
-                                       return false;
-                               }
-                               
-                               return true;
-                       });
-                       
-                       if (this.available.length !== 0) {
-                               this.timeoutId = setTimeout(function () {
-                                       return _this3.gc();
-                               }, GC_INTERVAL);
-                       } else {
-                               this.timeoutId = 0;
-                       }
-               };
-               
-               module.exports = Pool;
-               
-       },{}],10:[function(require,module,exports){
-// Add intermediate resizing steps when scaling down by a very large factor.
-//
-// For example, when resizing 10000x10000 down to 10x10, it'll resize it to
-// 300x300 first.
-//
-// It's needed because tiler has issues when the entire tile is scaled down
-// to a few pixels (1024px source tile with border size 3 should result in
-// at least 3+3+2 = 8px target tile, so max scale factor is 128 here).
-//
-// Also, adding intermediate steps can speed up processing if we use lower
-// quality algorithms for first stages.
-//
-               'use strict'; // min size = 0 results in infinite loop,
-// min size = 1 can consume large amount of memory
-               
-               var MIN_INNER_TILE_SIZE = 2;
-               
-               module.exports = function createStages(fromWidth, fromHeight, toWidth, toHeight, srcTileSize, destTileBorder) {
-                       var scaleX = toWidth / fromWidth;
-                       var scaleY = toHeight / fromHeight; // derived from createRegions equation:
-                       // innerTileWidth = pixelFloor(srcTileSize * scaleX) - 2 * destTileBorder;
-                       
-                       var minScale = (2 * destTileBorder + MIN_INNER_TILE_SIZE + 1) / srcTileSize; // refuse to scale image multiple times by less than twice each time,
-                       // it could only happen because of invalid options
-                       
-                       if (minScale > 0.5) return [[toWidth, toHeight]];
-                       var stageCount = Math.ceil(Math.log(Math.min(scaleX, scaleY)) / Math.log(minScale)); // no additional resizes are necessary,
-                       // stageCount can be zero or be negative when enlarging the image
-                       
-                       if (stageCount <= 1) return [[toWidth, toHeight]];
-                       var result = [];
-                       
-                       for (var i = 0; i < stageCount; i++) {
-                               var width = Math.round(Math.pow(Math.pow(fromWidth, stageCount - i - 1) * Math.pow(toWidth, i + 1), 1 / stageCount));
-                               var height = Math.round(Math.pow(Math.pow(fromHeight, stageCount - i - 1) * Math.pow(toHeight, i + 1), 1 / stageCount));
-                               result.push([width, height]);
-                       }
-                       
-                       return result;
-               };
-               
-       },{}],11:[function(require,module,exports){
-// Split original image into multiple 1024x1024 chunks to reduce memory usage
-// (images have to be unpacked into typed arrays for resizing) and allow
-// parallel processing of multiple tiles at a time.
-//
-               'use strict';
-               /*
-                * pixelFloor and pixelCeil are modified versions of Math.floor and Math.ceil
-                * functions which take into account floating point arithmetic errors.
-                * Those errors can cause undesired increments/decrements of sizes and offsets:
-                * Math.ceil(36 / (36 / 500)) = 501
-                * pixelCeil(36 / (36 / 500)) = 500
-                */
-               
-               var PIXEL_EPSILON = 1e-5;
-               
-               function pixelFloor(x) {
-                       var nearest = Math.round(x);
-                       
-                       if (Math.abs(x - nearest) < PIXEL_EPSILON) {
-                               return nearest;
-                       }
-                       
-                       return Math.floor(x);
-               }
-               
-               function pixelCeil(x) {
-                       var nearest = Math.round(x);
-                       
-                       if (Math.abs(x - nearest) < PIXEL_EPSILON) {
-                               return nearest;
-                       }
-                       
-                       return Math.ceil(x);
-               }
-               
-               module.exports = function createRegions(options) {
-                       var scaleX = options.toWidth / options.width;
-                       var scaleY = options.toHeight / options.height;
-                       var innerTileWidth = pixelFloor(options.srcTileSize * scaleX) - 2 * options.destTileBorder;
-                       var innerTileHeight = pixelFloor(options.srcTileSize * scaleY) - 2 * options.destTileBorder; // prevent infinite loop, this should never happen
-                       
-                       if (innerTileWidth < 1 || innerTileHeight < 1) {
-                               throw new Error('Internal error in pica: target tile width/height is too small.');
-                       }
-                       
-                       var x, y;
-                       var innerX, innerY, toTileWidth, toTileHeight;
-                       var tiles = [];
-                       var tile; // we go top-to-down instead of left-to-right to make image displayed from top to
-                       // doesn in the browser
-                       
-                       for (innerY = 0; innerY < options.toHeight; innerY += innerTileHeight) {
-                               for (innerX = 0; innerX < options.toWidth; innerX += innerTileWidth) {
-                                       x = innerX - options.destTileBorder;
-                                       
-                                       if (x < 0) {
-                                               x = 0;
-                                       }
-                                       
-                                       toTileWidth = innerX + innerTileWidth + options.destTileBorder - x;
-                                       
-                                       if (x + toTileWidth >= options.toWidth) {
-                                               toTileWidth = options.toWidth - x;
-                                       }
-                                       
-                                       y = innerY - options.destTileBorder;
-                                       
-                                       if (y < 0) {
-                                               y = 0;
-                                       }
-                                       
-                                       toTileHeight = innerY + innerTileHeight + options.destTileBorder - y;
-                                       
-                                       if (y + toTileHeight >= options.toHeight) {
-                                               toTileHeight = options.toHeight - y;
-                                       }
-                                       
-                                       tile = {
-                                               toX: x,
-                                               toY: y,
-                                               toWidth: toTileWidth,
-                                               toHeight: toTileHeight,
-                                               toInnerX: innerX,
-                                               toInnerY: innerY,
-                                               toInnerWidth: innerTileWidth,
-                                               toInnerHeight: innerTileHeight,
-                                               offsetX: x / scaleX - pixelFloor(x / scaleX),
-                                               offsetY: y / scaleY - pixelFloor(y / scaleY),
-                                               scaleX: scaleX,
-                                               scaleY: scaleY,
-                                               x: pixelFloor(x / scaleX),
-                                               y: pixelFloor(y / scaleY),
-                                               width: pixelCeil(toTileWidth / scaleX),
-                                               height: pixelCeil(toTileHeight / scaleY)
-                                       };
-                                       tiles.push(tile);
-                               }
-                       }
-                       
-                       return tiles;
-               };
-               
-       },{}],12:[function(require,module,exports){
-               'use strict';
-               
-               function objClass(obj) {
-                       return Object.prototype.toString.call(obj);
-               }
-               
-               module.exports.isCanvas = function isCanvas(element) {
-                       //return (element.nodeName && element.nodeName.toLowerCase() === 'canvas') ||
-                       var cname = objClass(element);
-                       return cname === '[object HTMLCanvasElement]'
-                               /* browser */
-                               || cname === '[object Canvas]'
-                               /* node-canvas */
-                               ;
-               };
-               
-               module.exports.isImage = function isImage(element) {
-                       //return element.nodeName && element.nodeName.toLowerCase() === 'img';
-                       return objClass(element) === '[object HTMLImageElement]';
-               };
-               
-               module.exports.limiter = function limiter(concurrency) {
-                       var active = 0,
-                               queue = [];
-                       
-                       function roll() {
-                               if (active < concurrency && queue.length) {
-                                       active++;
-                                       queue.shift()();
-                               }
-                       }
-                       
-                       return function limit(fn) {
-                               return new Promise(function (resolve, reject) {
-                                       queue.push(function () {
-                                               fn().then(function (result) {
-                                                       resolve(result);
-                                                       active--;
-                                                       roll();
-                                               }, function (err) {
-                                                       reject(err);
-                                                       active--;
-                                                       roll();
-                                               });
-                                       });
-                                       roll();
-                               });
-                       };
-               };
-               
-               module.exports.cib_quality_name = function cib_quality_name(num) {
-                       switch (num) {
-                               case 0:
-                                       return 'pixelated';
-                               
-                               case 1:
-                                       return 'low';
-                               
-                               case 2:
-                                       return 'medium';
-                       }
-                       
-                       return 'high';
-               };
-               
-               module.exports.cib_support = function cib_support() {
-                       return Promise.resolve().then(function () {
-                               if (typeof createImageBitmap === 'undefined' || typeof document === 'undefined') {
-                                       return false;
-                               }
-                               
-                               var c = document.createElement('canvas');
-                               c.width = 100;
-                               c.height = 100;
-                               return createImageBitmap(c, 0, 0, 100, 100, {
-                                       resizeWidth: 10,
-                                       resizeHeight: 10,
-                                       resizeQuality: 'high'
-                               }).then(function (bitmap) {
-                                       var status = bitmap.width === 10; // Branch below is filtered on upper level. We do not call resize
-                                       // detection for basic ImageBitmap.
-                                       //
-                                       // https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap
-                                       // old Crome 51 has ImageBitmap without .close(). Then this code
-                                       // will throw and return 'false' as expected.
-                                       //
-                                       
-                                       bitmap.close();
-                                       c = null;
-                                       return status;
-                               });
-                       }).catch(function () {
-                               return false;
-                       });
-               };
-               
-       },{}],13:[function(require,module,exports){
-// Web Worker wrapper for image resize function
-               'use strict';
-               
-               module.exports = function () {
-                       var MathLib = require('./mathlib');
-                       
-                       var mathLib;
-                       /* eslint-disable no-undef */
-                       
-                       onmessage = function onmessage(ev) {
-                               var opts = ev.data.opts;
-                               if (!mathLib) mathLib = new MathLib(ev.data.features); // Use multimath's sync auto-init. Avoid Promise use in old browsers,
-                               // because polyfills are not propagated to webworker.
-                               
-                               var result = mathLib.resizeAndUnsharp(opts);
-                               postMessage({
-                                       result: result
-                               }, [result.buffer]);
-                       };
-               };
-               
-       },{"./mathlib":1}],14:[function(require,module,exports){
-// Calculate Gaussian blur of an image using IIR filter
-// The method is taken from Intel's white paper and code example attached to it:
-// https://software.intel.com/en-us/articles/iir-gaussian-blur-filter
-// -implementation-using-intel-advanced-vector-extensions
-               
-               var a0, a1, a2, a3, b1, b2, left_corner, right_corner;
-               
-               function gaussCoef(sigma) {
-                       if (sigma < 0.5) {
-                               sigma = 0.5;
-                       }
-                       
-                       var a = Math.exp(0.726 * 0.726) / sigma,
-                               g1 = Math.exp(-a),
-                               g2 = Math.exp(-2 * a),
-                               k = (1 - g1) * (1 - g1) / (1 + 2 * a * g1 - g2);
-                       
-                       a0 = k;
-                       a1 = k * (a - 1) * g1;
-                       a2 = k * (a + 1) * g1;
-                       a3 = -k * g2;
-                       b1 = 2 * g1;
-                       b2 = -g2;
-                       left_corner = (a0 + a1) / (1 - b1 - b2);
-                       right_corner = (a2 + a3) / (1 - b1 - b2);
-                       
-                       // Attempt to force type to FP32.
-                       return new Float32Array([ a0, a1, a2, a3, b1, b2, left_corner, right_corner ]);
-               }
-               
-               function convolveMono16(src, out, line, coeff, width, height) {
-                       // takes src image and writes the blurred and transposed result into out
-                       
-                       var prev_src, curr_src, curr_out, prev_out, prev_prev_out;
-                       var src_index, out_index, line_index;
-                       var i, j;
-                       var coeff_a0, coeff_a1, coeff_b1, coeff_b2;
-                       
-                       for (i = 0; i < height; i++) {
-                               src_index = i * width;
-                               out_index = i;
-                               line_index = 0;
-                               
-                               // left to right
-                               prev_src = src[src_index];
-                               prev_prev_out = prev_src * coeff[6];
-                               prev_out = prev_prev_out;
-                               
-                               coeff_a0 = coeff[0];
-                               coeff_a1 = coeff[1];
-                               coeff_b1 = coeff[4];
-                               coeff_b2 = coeff[5];
-                               
-                               for (j = 0; j < width; j++) {
-                                       curr_src = src[src_index];
-                                       
-                                       curr_out = curr_src * coeff_a0 +
-                                               prev_src * coeff_a1 +
-                                               prev_out * coeff_b1 +
-                                               prev_prev_out * coeff_b2;
-                                       
-                                       prev_prev_out = prev_out;
-                                       prev_out = curr_out;
-                                       prev_src = curr_src;
-                                       
-                                       line[line_index] = prev_out;
-                                       line_index++;
-                                       src_index++;
-                               }
-                               
-                               src_index--;
-                               line_index--;
-                               out_index += height * (width - 1);
-                               
-                               // right to left
-                               prev_src = src[src_index];
-                               prev_prev_out = prev_src * coeff[7];
-                               prev_out = prev_prev_out;
-                               curr_src = prev_src;
-                               
-                               coeff_a0 = coeff[2];
-                               coeff_a1 = coeff[3];
-                               
-                               for (j = width - 1; j >= 0; j--) {
-                                       curr_out = curr_src * coeff_a0 +
-                                               prev_src * coeff_a1 +
-                                               prev_out * coeff_b1 +
-                                               prev_prev_out * coeff_b2;
-                                       
-                                       prev_prev_out = prev_out;
-                                       prev_out = curr_out;
-                                       
-                                       prev_src = curr_src;
-                                       curr_src = src[src_index];
-                                       
-                                       out[out_index] = line[line_index] + prev_out;
-                                       
-                                       src_index--;
-                                       line_index--;
-                                       out_index -= height;
-                               }
-                       }
-               }
-               
-               
-               function blurMono16(src, width, height, radius) {
-                       // Quick exit on zero radius
-                       if (!radius) { return; }
-                       
-                       var out      = new Uint16Array(src.length),
-                               tmp_line = new Float32Array(Math.max(width, height));
-                       
-                       var coeff = gaussCoef(radius);
-                       
-                       convolveMono16(src, out, tmp_line, coeff, width, height, radius);
-                       convolveMono16(out, src, tmp_line, coeff, height, width, radius);
-               }
-               
-               module.exports = blurMono16;
-               
-       },{}],15:[function(require,module,exports){
-               if (typeof Object.create === 'function') {
-                       // implementation from standard node.js 'util' module
-                       module.exports = function inherits(ctor, superCtor) {
-                               ctor.super_ = superCtor
-                               ctor.prototype = Object.create(superCtor.prototype, {
-                                       constructor: {
-                                               value: ctor,
-                                               enumerable: false,
-                                               writable: true,
-                                               configurable: true
-                                       }
-                               });
-                       };
-               } else {
-                       // old school shim for old browsers
-                       module.exports = function inherits(ctor, superCtor) {
-                               ctor.super_ = superCtor
-                               var TempCtor = function () {}
-                               TempCtor.prototype = superCtor.prototype
-                               ctor.prototype = new TempCtor()
-                               ctor.prototype.constructor = ctor
-                       }
-               }
-               
-       },{}],16:[function(require,module,exports){
-               'use strict';
-               
-               
-               var assign         = require('object-assign');
-               var base64decode   = require('./lib/base64decode');
-               var hasWebAssembly = require('./lib/wa_detect');
-               
-               
-               var DEFAULT_OPTIONS = {
-                       js: true,
-                       wasm: true
-               };
-               
-               
-               function MultiMath(options) {
-                       if (!(this instanceof MultiMath)) return new MultiMath(options);
-                       
-                       var opts = assign({}, DEFAULT_OPTIONS, options || {});
-                       
-                       this.options         = opts;
-                       
-                       this.__cache         = {};
-                       this.has_wasm        = hasWebAssembly();
-                       
-                       this.__init_promise  = null;
-                       this.__modules       = opts.modules || {};
-                       this.__memory        = null;
-                       this.__wasm          = {};
-                       
-                       this.__isLE = ((new Uint32Array((new Uint8Array([ 1, 0, 0, 0 ])).buffer))[0] === 1);
-                       
-                       if (!this.options.js && !this.options.wasm) {
-                               throw new Error('mathlib: at least "js" or "wasm" should be enabled');
-                       }
-               }
-               
-               
-               MultiMath.prototype.use = function (module) {
-                       this.__modules[module.name] = module;
-                       
-                       // Pin the best possible implementation
-                       if (!this.has_wasm || !this.options.wasm || !module.wasm_fn) {
-                               this[module.name] = module.fn;
-                       } else {
-                               this[module.name] = module.wasm_fn;
-                       }
-                       
-                       return this;
-               };
-               
-               
-               MultiMath.prototype.init = function () {
-                       if (this.__init_promise) return this.__init_promise;
-                       
-                       if (!this.options.js && this.options.wasm && !this.has_wasm) {
-                               return Promise.reject(new Error('mathlib: only "wasm" was enabled, but it\'s not supported'));
-                       }
-                       
-                       var self = this;
-                       
-                       this.__init_promise = Promise.all(Object.keys(self.__modules).map(function (name) {
-                                       var module = self.__modules[name];
-                                       
-                                       if (!self.has_wasm || !self.options.wasm || !module.wasm_fn) return null;
-                                       
-                                       // If already compiled - exit
-                                       if (self.__wasm[name]) return null;
-                                       
-                                       // Compile wasm source
-                                       return WebAssembly.compile(self.__base64decode(module.wasm_src))
-                                               .then(function (m) { self.__wasm[name] = m; });
-                               }))
-                               .then(function () { return self; });
-                       
-                       return this.__init_promise;
-               };
-
-
-////////////////////////////////////////////////////////////////////////////////
-// Methods below are for internal use from plugins
-
-
-// Simple decode base64 to typed array. Useful to load embedded webassembly
-// code. You probably don't need to call this method directly.
-//
-               MultiMath.prototype.__base64decode = base64decode;
-
-
-// Increase current memory to include specified number of bytes. Do nothing if
-// size is already ok. You probably don't need to call this method directly,
-// because it will be invoked from `.__instance()`.
-//
-               MultiMath.prototype.__reallocate = function mem_grow_to(bytes) {
-                       if (!this.__memory) {
-                               this.__memory = new WebAssembly.Memory({
-                                       initial: Math.ceil(bytes / (64 * 1024))
-                               });
-                               return this.__memory;
-                       }
-                       
-                       var mem_size = this.__memory.buffer.byteLength;
-                       
-                       if (mem_size < bytes) {
-                               this.__memory.grow(Math.ceil((bytes - mem_size) / (64 * 1024)));
-                       }
-                       
-                       return this.__memory;
-               };
-
-
-// Returns instantinated webassembly item by name, with specified memory size
-// and environment.
-// - use cache if available
-// - do sync module init, if async init was not called earlier
-// - allocate memory if not enougth
-// - can export functions to webassembly via "env_extra",
-//   for example, { exp: Math.exp }
-//
-               MultiMath.prototype.__instance = function instance(name, memsize, env_extra) {
-                       if (memsize) this.__reallocate(memsize);
-                       
-                       // If .init() was not called, do sync compile
-                       if (!this.__wasm[name]) {
-                               var module = this.__modules[name];
-                               this.__wasm[name] = new WebAssembly.Module(this.__base64decode(module.wasm_src));
-                       }
-                       
-                       if (!this.__cache[name]) {
-                               var env_base = {
-                                       memoryBase: 0,
-                                       memory: this.__memory,
-                                       tableBase: 0,
-                                       table: new WebAssembly.Table({ initial: 0, element: 'anyfunc' })
-                               };
-                               
-                               this.__cache[name] = new WebAssembly.Instance(this.__wasm[name], {
-                                       env: assign(env_base, env_extra || {})
-                               });
-                       }
-                       
-                       return this.__cache[name];
-               };
-
-
-// Helper to calculate memory aligh for pointers. Webassembly does not require
-// this, but you may wish to experiment. Default base = 8;
-//
-               MultiMath.prototype.__align = function align(number, base) {
-                       base = base || 8;
-                       var reminder = number % base;
-                       return number + (reminder ? base - reminder : 0);
-               };
-               
-               
-               module.exports = MultiMath;
-               
-       },{"./lib/base64decode":17,"./lib/wa_detect":23,"object-assign":24}],17:[function(require,module,exports){
-// base64 decode str -> Uint8Array, to load WA modules
-//
-               'use strict';
-               
-               
-               var BASE64_MAP = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
-               
-               
-               module.exports = function base64decode(str) {
-                       var input = str.replace(/[\r\n=]/g, ''), // remove CR/LF & padding to simplify scan
-                               max   = input.length;
-                       
-                       var out = new Uint8Array((max * 3) >> 2);
-                       
-                       // Collect by 6*4 bits (3 bytes)
-                       
-                       var bits = 0;
-                       var ptr  = 0;
-                       
-                       for (var idx = 0; idx < max; idx++) {
-                               if ((idx % 4 === 0) && idx) {
-                                       out[ptr++] = (bits >> 16) & 0xFF;
-                                       out[ptr++] = (bits >> 8) & 0xFF;
-                                       out[ptr++] = bits & 0xFF;
-                               }
-                               
-                               bits = (bits << 6) | BASE64_MAP.indexOf(input.charAt(idx));
-                       }
-                       
-                       // Dump tail
-                       
-                       var tailbits = (max % 4) * 6;
-                       
-                       if (tailbits === 0) {
-                               out[ptr++] = (bits >> 16) & 0xFF;
-                               out[ptr++] = (bits >> 8) & 0xFF;
-                               out[ptr++] = bits & 0xFF;
-                       } else if (tailbits === 18) {
-                               out[ptr++] = (bits >> 10) & 0xFF;
-                               out[ptr++] = (bits >> 2) & 0xFF;
-                       } else if (tailbits === 12) {
-                               out[ptr++] = (bits >> 4) & 0xFF;
-                       }
-                       
-                       return out;
-               };
-               
-       },{}],18:[function(require,module,exports){
-// Calculates 16-bit precision HSL lightness from 8-bit rgba buffer
-//
-               'use strict';
-               
-               
-               module.exports = function hsl_l16_js(img, width, height) {
-                       var size = width * height;
-                       var out = new Uint16Array(size);
-                       var r, g, b, min, max;
-                       for (var i = 0; i < size; i++) {
-                               r = img[4 * i];
-                               g = img[4 * i + 1];
-                               b = img[4 * i + 2];
-                               max = (r >= g && r >= b) ? r : (g >= b && g >= r) ? g : b;
-                               min = (r <= g && r <= b) ? r : (g <= b && g <= r) ? g : b;
-                               out[i] = (max + min) * 257 >> 1;
-                       }
-                       return out;
-               };
-               
-       },{}],19:[function(require,module,exports){
-               'use strict';
-               
-               module.exports = {
-                       name:     'unsharp_mask',
-                       fn:       require('./unsharp_mask'),
-                       wasm_fn:  require('./unsharp_mask_wasm'),
-                       wasm_src: require('./unsharp_mask_wasm_base64')
-               };
-               
-       },{"./unsharp_mask":20,"./unsharp_mask_wasm":21,"./unsharp_mask_wasm_base64":22}],20:[function(require,module,exports){
-// Unsharp mask filter
-//
-// http://stackoverflow.com/a/23322820/1031804
-// USM(O) = O + (2 * (Amount / 100) * (O - GB))
-// GB - gaussian blur.
-//
-// Image is converted from RGB to HSL, unsharp mask is applied to the
-// lightness channel and then image is converted back to RGB.
-//
-               'use strict';
-               
-               
-               var glur_mono16 = require('glur/mono16');
-               var hsl_l16     = require('./hsl_l16');
-               
-               
-               module.exports = function unsharp(img, width, height, amount, radius, threshold) {
-                       var r, g, b;
-                       var h, s, l;
-                       var min, max;
-                       var m1, m2, hShifted;
-                       var diff, iTimes4;
-                       
-                       if (amount === 0 || radius < 0.5) {
-                               return;
-                       }
-                       if (radius > 2.0) {
-                               radius = 2.0;
-                       }
-                       
-                       var lightness = hsl_l16(img, width, height);
-                       
-                       var blured = new Uint16Array(lightness); // copy, because blur modify src
-                       
-                       glur_mono16(blured, width, height, radius);
-                       
-                       var amountFp = (amount / 100 * 0x1000 + 0.5)|0;
-                       var thresholdFp = (threshold * 257)|0;
-                       
-                       var size = width * height;
-                       
-                       /* eslint-disable indent */
-                       for (var i = 0; i < size; i++) {
-                               diff = 2 * (lightness[i] - blured[i]);
-                               
-                               if (Math.abs(diff) >= thresholdFp) {
-                                       iTimes4 = i * 4;
-                                       r = img[iTimes4];
-                                       g = img[iTimes4 + 1];
-                                       b = img[iTimes4 + 2];
-                                       
-                                       // convert RGB to HSL
-                                       // take RGB, 8-bit unsigned integer per each channel
-                                       // save HSL, H and L are 16-bit unsigned integers, S is 12-bit unsigned integer
-                                       // math is taken from here: http://www.easyrgb.com/index.php?X=MATH&H=18
-                                       // and adopted to be integer (fixed point in fact) for sake of performance
-                                       max = (r >= g && r >= b) ? r : (g >= r && g >= b) ? g : b; // min and max are in [0..0xff]
-                                       min = (r <= g && r <= b) ? r : (g <= r && g <= b) ? g : b;
-                                       l = (max + min) * 257 >> 1; // l is in [0..0xffff] that is caused by multiplication by 257
-                                       
-                                       if (min === max) {
-                                               h = s = 0;
-                                       } else {
-                                               s = (l <= 0x7fff) ?
-                                                       (((max - min) * 0xfff) / (max + min))|0 :
-                                                       (((max - min) * 0xfff) / (2 * 0xff - max - min))|0; // s is in [0..0xfff]
-                                               // h could be less 0, it will be fixed in backward conversion to RGB, |h| <= 0xffff / 6
-                                               h = (r === max) ? (((g - b) * 0xffff) / (6 * (max - min)))|0
-                                                       : (g === max) ? 0x5555 + ((((b - r) * 0xffff) / (6 * (max - min)))|0) // 0x5555 == 0xffff / 3
-                                                               : 0xaaaa + ((((r - g) * 0xffff) / (6 * (max - min)))|0); // 0xaaaa == 0xffff * 2 / 3
-                                       }
-                                       
-                                       // add unsharp mask mask to the lightness channel
-                                       l += (amountFp * diff + 0x800) >> 12;
-                                       if (l > 0xffff) {
-                                               l = 0xffff;
-                                       } else if (l < 0) {
-                                               l = 0;
-                                       }
-                                       
-                                       // convert HSL back to RGB
-                                       // for information about math look above
-                                       if (s === 0) {
-                                               r = g = b = l >> 8;
-                                       } else {
-                                               m2 = (l <= 0x7fff) ? (l * (0x1000 + s) + 0x800) >> 12 :
-                                                       l  + (((0xffff - l) * s + 0x800) >>  12);
-                                               m1 = 2 * l - m2 >> 8;
-                                               m2 >>= 8;
-                                               // save result to RGB channels
-                                               // R channel
-                                               hShifted = (h + 0x5555) & 0xffff; // 0x5555 == 0xffff / 3
-                                               r = (hShifted >= 0xaaaa) ? m1 // 0xaaaa == 0xffff * 2 / 3
-                                                       : (hShifted >= 0x7fff) ?  m1 + ((m2 - m1) * 6 * (0xaaaa - hShifted) + 0x8000 >> 16)
-                                                               : (hShifted >= 0x2aaa) ? m2 // 0x2aaa == 0xffff / 6
-                                                                       : m1 + ((m2 - m1) * 6 * hShifted + 0x8000 >> 16);
-                                               // G channel
-                                               hShifted = h & 0xffff;
-                                               g = (hShifted >= 0xaaaa) ? m1 // 0xaaaa == 0xffff * 2 / 3
-                                                       : (hShifted >= 0x7fff) ?  m1 + ((m2 - m1) * 6 * (0xaaaa - hShifted) + 0x8000 >> 16)
-                                                               : (hShifted >= 0x2aaa) ? m2 // 0x2aaa == 0xffff / 6
-                                                                       : m1 + ((m2 - m1) * 6 * hShifted + 0x8000 >> 16);
-                                               // B channel
-                                               hShifted = (h - 0x5555) & 0xffff;
-                                               b = (hShifted >= 0xaaaa) ? m1 // 0xaaaa == 0xffff * 2 / 3
-                                                       : (hShifted >= 0x7fff) ?  m1 + ((m2 - m1) * 6 * (0xaaaa - hShifted) + 0x8000 >> 16)
-                                                               : (hShifted >= 0x2aaa) ? m2 // 0x2aaa == 0xffff / 6
-                                                                       : m1 + ((m2 - m1) * 6 * hShifted + 0x8000 >> 16);
-                                       }
-                                       
-                                       img[iTimes4] = r;
-                                       img[iTimes4 + 1] = g;
-                                       img[iTimes4 + 2] = b;
-                               }
-                       }
-               };
-               
-       },{"./hsl_l16":18,"glur/mono16":14}],21:[function(require,module,exports){
-               'use strict';
-               
-               
-               module.exports = function unsharp(img, width, height, amount, radius, threshold) {
-                       if (amount === 0 || radius < 0.5) {
-                               return;
-                       }
-                       
-                       if (radius > 2.0) {
-                               radius = 2.0;
-                       }
-                       
-                       var pixels = width * height;
-                       
-                       var img_bytes_cnt        = pixels * 4;
-                       var hsl_bytes_cnt        = pixels * 2;
-                       var blur_bytes_cnt       = pixels * 2;
-                       var blur_line_byte_cnt   = Math.max(width, height) * 4; // float32 array
-                       var blur_coeffs_byte_cnt = 8 * 4; // float32 array
-                       
-                       var img_offset         = 0;
-                       var hsl_offset         = img_bytes_cnt;
-                       var blur_offset        = hsl_offset + hsl_bytes_cnt;
-                       var blur_tmp_offset    = blur_offset + blur_bytes_cnt;
-                       var blur_line_offset   = blur_tmp_offset + blur_bytes_cnt;
-                       var blur_coeffs_offset = blur_line_offset + blur_line_byte_cnt;
-                       
-                       var instance = this.__instance(
-                               'unsharp_mask',
-                               img_bytes_cnt + hsl_bytes_cnt + blur_bytes_cnt * 2 + blur_line_byte_cnt + blur_coeffs_byte_cnt,
-                               { exp: Math.exp }
-                       );
-                       
-                       // 32-bit copy is much faster in chrome
-                       var img32 = new Uint32Array(img.buffer);
-                       var mem32 = new Uint32Array(this.__memory.buffer);
-                       mem32.set(img32);
-                       
-                       // HSL
-                       var fn = instance.exports.hsl_l16 || instance.exports._hsl_l16;
-                       fn(img_offset, hsl_offset, width, height);
-                       
-                       // BLUR
-                       fn = instance.exports.blurMono16 || instance.exports._blurMono16;
-                       fn(hsl_offset, blur_offset, blur_tmp_offset,
-                               blur_line_offset, blur_coeffs_offset, width, height, radius);
-                       
-                       // UNSHARP
-                       fn = instance.exports.unsharp || instance.exports._unsharp;
-                       fn(img_offset, img_offset, hsl_offset,
-                               blur_offset, width, height, amount, threshold);
-                       
-                       // 32-bit copy is much faster in chrome
-                       img32.set(new Uint32Array(this.__memory.buffer, 0, pixels));
-               };
-               
-       },{}],22:[function(require,module,exports){
-// This is autogenerated file from math.wasm, don't edit.
-//
-               'use strict';
-               
-               /* eslint-disable max-len */
-               module.exports = 'AGFzbQEAAAABMQZgAXwBfGACfX8AYAZ/f39/f38AYAh/f39/f39/fQBgBH9/f38AYAh/f39/f39/fwACGQIDZW52A2V4cAAAA2VudgZtZW1vcnkCAAEDBgUBAgMEBQQEAXAAAAdMBRZfX2J1aWxkX2dhdXNzaWFuX2NvZWZzAAEOX19nYXVzczE2X2xpbmUAAgpibHVyTW9ubzE2AAMHaHNsX2wxNgAEB3Vuc2hhcnAABQkBAAqJEAXZAQEGfAJAIAFE24a6Q4Ia+z8gALujIgOaEAAiBCAEoCIGtjgCECABIANEAAAAAAAAAMCiEAAiBbaMOAIUIAFEAAAAAAAA8D8gBKEiAiACoiAEIAMgA6CiRAAAAAAAAPA/oCAFoaMiArY4AgAgASAEIANEAAAAAAAA8L+gIAKioiIHtjgCBCABIAQgA0QAAAAAAADwP6AgAqKiIgO2OAIIIAEgBSACoiIEtow4AgwgASACIAegIAVEAAAAAAAA8D8gBqGgIgKjtjgCGCABIAMgBKEgAqO2OAIcCwu3AwMDfwR9CHwCQCADKgIUIQkgAyoCECEKIAMqAgwhCyADKgIIIQwCQCAEQX9qIgdBAEgiCA0AIAIgAC8BALgiDSADKgIYu6IiDiAJuyIQoiAOIAq7IhGiIA0gAyoCBLsiEqIgAyoCALsiEyANoqCgoCIPtjgCACACQQRqIQIgAEECaiEAIAdFDQAgBCEGA0AgAiAOIBCiIA8iDiARoiANIBKiIBMgAC8BALgiDaKgoKAiD7Y4AgAgAkEEaiECIABBAmohACAGQX9qIgZBAUoNAAsLAkAgCA0AIAEgByAFbEEBdGogAEF+ai8BACIIuCINIAu7IhGiIA0gDLsiEqKgIA0gAyoCHLuiIg4gCrsiE6KgIA4gCbsiFKKgIg8gAkF8aioCALugqzsBACAHRQ0AIAJBeGohAiAAQXxqIQBBACAFQQF0ayEHIAEgBSAEQQF0QXxqbGohBgNAIAghAyAALwEAIQggBiANIBGiIAO4Ig0gEqKgIA8iECAToqAgDiAUoqAiDyACKgIAu6CrOwEAIAYgB2ohBiAAQX5qIQAgAkF8aiECIBAhDiAEQX9qIgRBAUoNAAsLCwvfAgIDfwZ8AkAgB0MAAAAAWw0AIARE24a6Q4Ia+z8gB0MAAAA/l7ujIgyaEAAiDSANoCIPtjgCECAEIAxEAAAAAAAAAMCiEAAiDraMOAIUIAREAAAAAAAA8D8gDaEiCyALoiANIAwgDKCiRAAAAAAAAPA/oCAOoaMiC7Y4AgAgBCANIAxEAAAAAAAA8L+gIAuioiIQtjgCBCAEIA0gDEQAAAAAAADwP6AgC6KiIgy2OAIIIAQgDiALoiINtow4AgwgBCALIBCgIA5EAAAAAAAA8D8gD6GgIgujtjgCGCAEIAwgDaEgC6O2OAIcIAYEQCAFQQF0IQogBiEJIAIhCANAIAAgCCADIAQgBSAGEAIgACAKaiEAIAhBAmohCCAJQX9qIgkNAAsLIAVFDQAgBkEBdCEIIAUhAANAIAIgASADIAQgBiAFEAIgAiAIaiECIAFBAmohASAAQX9qIgANAAsLC7wBAQV/IAMgAmwiAwRAQQAgA2shBgNAIAAoAgAiBEEIdiIHQf8BcSECAn8gBEH/AXEiAyAEQRB2IgRB/wFxIgVPBEAgAyIIIAMgAk8NARoLIAQgBCAHIAIgA0kbIAIgBUkbQf8BcQshCAJAIAMgAk0EQCADIAVNDQELIAQgByAEIAMgAk8bIAIgBUsbQf8BcSEDCyAAQQRqIQAgASADIAhqQYECbEEBdjsBACABQQJqIQEgBkEBaiIGDQALCwvTBgEKfwJAIAazQwAAgEWUQwAAyEKVu0QAAAAAAADgP6CqIQ0gBSAEbCILBEAgB0GBAmwhDgNAQQAgAi8BACADLwEAayIGQQF0IgdrIAcgBkEASBsgDk8EQCAAQQJqLQAAIQUCfyAALQAAIgYgAEEBai0AACIESSIJRQRAIAYiCCAGIAVPDQEaCyAFIAUgBCAEIAVJGyAGIARLGwshCAJ/IAYgBE0EQCAGIgogBiAFTQ0BGgsgBSAFIAQgBCAFSxsgCRsLIgogCGoiD0GBAmwiEEEBdiERQQAhDAJ/QQAiCSAIIApGDQAaIAggCmsiCUH/H2wgD0H+AyAIayAKayAQQYCABEkbbSEMIAYgCEYEQCAEIAVrQf//A2wgCUEGbG0MAQsgBSAGayAGIARrIAQgCEYiBhtB//8DbCAJQQZsbUHVqgFBqtUCIAYbagshCSARIAcgDWxBgBBqQQx1aiIGQQAgBkEAShsiBkH//wMgBkH//wNIGyEGAkACfwJAIAxB//8DcSIFBEAgBkH//wFKDQEgBUGAIGogBmxBgBBqQQx2DAILIAZBCHYiBiEFIAYhBAwCCyAFIAZB//8Dc2xBgBBqQQx2IAZqCyIFQQh2IQcgBkEBdCAFa0EIdiIGIQQCQCAJQdWqAWpB//8DcSIFQanVAksNACAFQf//AU8EQEGq1QIgBWsgByAGa2xBBmxBgIACakEQdiAGaiEEDAELIAchBCAFQanVAEsNACAFIAcgBmtsQQZsQYCAAmpBEHYgBmohBAsCfyAGIgUgCUH//wNxIghBqdUCSw0AGkGq1QIgCGsgByAGa2xBBmxBgIACakEQdiAGaiAIQf//AU8NABogByIFIAhBqdUASw0AGiAIIAcgBmtsQQZsQYCAAmpBEHYgBmoLIQUgCUGr1QJqQf//A3EiCEGp1QJLDQAgCEH//wFPBEBBqtUCIAhrIAcgBmtsQQZsQYCAAmpBEHYgBmohBgwBCyAIQanVAEsEQCAHIQYMAQsgCCAHIAZrbEEGbEGAgAJqQRB2IAZqIQYLIAEgBDoAACABQQFqIAU6AAAgAUECaiAGOgAACyADQQJqIQMgAkECaiECIABBBGohACABQQRqIQEgC0F/aiILDQALCwsL';
-               
-       },{}],23:[function(require,module,exports){
-// Detect WebAssembly support.
-// - Check global WebAssembly object
-// - Try to load simple module (can be disabled via CSP)
-//
-               'use strict';
-               
-               
-               var wa;
-               
-               
-               module.exports = function hasWebAssembly() {
-                       // use cache if called before;
-                       if (typeof wa !== 'undefined') return wa;
-                       
-                       wa = false;
-                       
-                       if (typeof WebAssembly === 'undefined') return wa;
-                       
-                       // If WebAssenbly is disabled, code can throw on compile
-                       try {
-                               // https://github.com/brion/min-wasm-fail/blob/master/min-wasm-fail.in.js
-                               // Additional check that WA internals are correct
-                               
-                               /* eslint-disable comma-spacing, max-len */
-                               var bin      = new Uint8Array([ 0,97,115,109,1,0,0,0,1,6,1,96,1,127,1,127,3,2,1,0,5,3,1,0,1,7,8,1,4,116,101,115,116,0,0,10,16,1,14,0,32,0,65,1,54,2,0,32,0,40,2,0,11 ]);
-                               var module   = new WebAssembly.Module(bin);
-                               var instance = new WebAssembly.Instance(module, {});
-                               
-                               // test storing to and loading from a non-zero location via a parameter.
-                               // Safari on iOS 11.2.5 returns 0 unexpectedly at non-zero locations
-                               if (instance.exports.test(4) !== 0) wa = true;
-                               
-                               return wa;
-                       } catch (__) {}
-                       
-                       return wa;
-               };
-               
-       },{}],24:[function(require,module,exports){
-               /*
-               object-assign
-               (c) Sindre Sorhus
-               @license MIT
-               */
-               
-               'use strict';
-               /* eslint-disable no-unused-vars */
-               var getOwnPropertySymbols = Object.getOwnPropertySymbols;
-               var hasOwnProperty = Object.prototype.hasOwnProperty;
-               var propIsEnumerable = Object.prototype.propertyIsEnumerable;
-               
-               function toObject(val) {
-                       if (val === null || val === undefined) {
-                               throw new TypeError('Object.assign cannot be called with null or undefined');
-                       }
-                       
-                       return Object(val);
-               }
-               
-               function shouldUseNative() {
-                       try {
-                               if (!Object.assign) {
-                                       return false;
-                               }
-                               
-                               // Detect buggy property enumeration order in older V8 versions.
-                               
-                               // https://bugs.chromium.org/p/v8/issues/detail?id=4118
-                               var test1 = new String('abc');  // eslint-disable-line no-new-wrappers
-                               test1[5] = 'de';
-                               if (Object.getOwnPropertyNames(test1)[0] === '5') {
-                                       return false;
-                               }
-                               
-                               // https://bugs.chromium.org/p/v8/issues/detail?id=3056
-                               var test2 = {};
-                               for (var i = 0; i < 10; i++) {
-                                       test2['_' + String.fromCharCode(i)] = i;
-                               }
-                               var order2 = Object.getOwnPropertyNames(test2).map(function (n) {
-                                       return test2[n];
-                               });
-                               if (order2.join('') !== '0123456789') {
-                                       return false;
-                               }
-                               
-                               // https://bugs.chromium.org/p/v8/issues/detail?id=3056
-                               var test3 = {};
-                               'abcdefghijklmnopqrst'.split('').forEach(function (letter) {
-                                       test3[letter] = letter;
-                               });
-                               if (Object.keys(Object.assign({}, test3)).join('') !==
-                                       'abcdefghijklmnopqrst') {
-                                       return false;
-                               }
-                               
-                               return true;
-                       } catch (err) {
-                               // We don't expect any of the above to throw, but better to be safe.
-                               return false;
-                       }
-               }
-               
-               module.exports = shouldUseNative() ? Object.assign : function (target, source) {
-                       var from;
-                       var to = toObject(target);
-                       var symbols;
-                       
-                       for (var s = 1; s < arguments.length; s++) {
-                               from = Object(arguments[s]);
-                               
-                               for (var key in from) {
-                                       if (hasOwnProperty.call(from, key)) {
-                                               to[key] = from[key];
-                                       }
-                               }
-                               
-                               if (getOwnPropertySymbols) {
-                                       symbols = getOwnPropertySymbols(from);
-                                       for (var i = 0; i < symbols.length; i++) {
-                                               if (propIsEnumerable.call(from, symbols[i])) {
-                                                       to[symbols[i]] = from[symbols[i]];
-                                               }
-                                       }
-                               }
-                       }
-                       
-                       return to;
-               };
-               
-       },{}],25:[function(require,module,exports){
-               var bundleFn = arguments[3];
-               var sources = arguments[4];
-               var cache = arguments[5];
-               
-               var stringify = JSON.stringify;
-               
-               module.exports = function (fn, options) {
-                       var wkey;
-                       var cacheKeys = Object.keys(cache);
-                       
-                       for (var i = 0, l = cacheKeys.length; i < l; i++) {
-                               var key = cacheKeys[i];
-                               var exp = cache[key].exports;
-                               // Using babel as a transpiler to use esmodule, the export will always
-                               // be an object with the default export as a property of it. To ensure
-                               // the existing api and babel esmodule exports are both supported we
-                               // check for both
-                               if (exp === fn || exp && exp.default === fn) {
-                                       wkey = key;
-                                       break;
-                               }
-                       }
-                       
-                       if (!wkey) {
-                               wkey = Math.floor(Math.pow(16, 8) * Math.random()).toString(16);
-                               var wcache = {};
-                               for (var i = 0, l = cacheKeys.length; i < l; i++) {
-                                       var key = cacheKeys[i];
-                                       wcache[key] = key;
-                               }
-                               sources[wkey] = [
-                                       'function(require,module,exports){' + fn + '(self); }',
-                                       wcache
-                               ];
-                       }
-                       var skey = Math.floor(Math.pow(16, 8) * Math.random()).toString(16);
-                       
-                       var scache = {}; scache[wkey] = wkey;
-                       sources[skey] = [
-                               'function(require,module,exports){' +
-                               // try to call default if defined to also support babel esmodule exports
-                               'var f = require(' + stringify(wkey) + ');' +
-                               '(f.default ? f.default : f)(self);' +
-                               '}',
-                               scache
-                       ];
-                       
-                       var workerSources = {};
-                       resolveSources(skey);
-                       
-                       function resolveSources(key) {
-                               workerSources[key] = true;
-                               
-                               for (var depPath in sources[key][1]) {
-                                       var depKey = sources[key][1][depPath];
-                                       if (!workerSources[depKey]) {
-                                               resolveSources(depKey);
-                                       }
-                               }
-                       }
-                       
-                       var src = '(' + bundleFn + ')({'
-                               + Object.keys(workerSources).map(function (key) {
-                                       return stringify(key) + ':['
-                                               + sources[key][0]
-                                               + ',' + stringify(sources[key][1]) + ']'
-                                               ;
-                               }).join(',')
-                               + '},{},[' + stringify(skey) + '])'
-                       ;
-                       
-                       var URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
-                       
-                       var blob = new Blob([src], { type: 'text/javascript' });
-                       if (options && options.bare) { return blob; }
-                       var workerUrl = URL.createObjectURL(blob);
-                       var worker = new Worker(workerUrl);
-                       worker.objectURL = workerUrl;
-                       return worker;
-               };
-               
-       },{}],"/":[function(require,module,exports){
-               'use strict';
-               
-               function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }
-               
-               function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); }
-               
-               function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
-               
-               function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
-               
-               var assign = require('object-assign');
-               
-               var webworkify = require('webworkify');
-               
-               var MathLib = require('./lib/mathlib');
-               
-               var Pool = require('./lib/pool');
-               
-               var utils = require('./lib/utils');
-               
-               var worker = require('./lib/worker');
-               
-               var createStages = require('./lib/stepper');
-               
-               var createRegions = require('./lib/tiler'); // Deduplicate pools & limiters with the same configs
-// when user creates multiple pica instances.
-               
-               
-               var singletones = {};
-               var NEED_SAFARI_FIX = false;
-               
-               try {
-                       if (typeof navigator !== 'undefined' && navigator.userAgent) {
-                               NEED_SAFARI_FIX = navigator.userAgent.indexOf('Safari') >= 0;
-                       }
-               } catch (e) {}
-               
-               var concurrency = 1;
-               
-               if (typeof navigator !== 'undefined') {
-                       concurrency = Math.min(navigator.hardwareConcurrency || 1, 4);
-               }
-               
-               var DEFAULT_PICA_OPTS = {
-                       tile: 1024,
-                       concurrency: concurrency,
-                       features: ['js', 'wasm', 'ww'],
-                       idle: 2000
-               };
-               var DEFAULT_RESIZE_OPTS = {
-                       quality: 3,
-                       alpha: false,
-                       unsharpAmount: 0,
-                       unsharpRadius: 0.0,
-                       unsharpThreshold: 0
-               };
-               var CAN_NEW_IMAGE_DATA;
-               var CAN_CREATE_IMAGE_BITMAP;
-               
-               function workerFabric() {
-                       return {
-                               value: webworkify(worker),
-                               destroy: function destroy() {
-                                       this.value.terminate();
-                                       
-                                       if (typeof window !== 'undefined') {
-                                               var url = window.URL || window.webkitURL || window.mozURL || window.msURL;
-                                               
-                                               if (url && url.revokeObjectURL && this.value.objectURL) {
-                                                       url.revokeObjectURL(this.value.objectURL);
-                                               }
-                                       }
-                               }
-                       };
-               } ////////////////////////////////////////////////////////////////////////////////
-// API methods
-               
-               
-               function Pica(options) {
-                       if (!(this instanceof Pica)) return new Pica(options);
-                       this.options = assign({}, DEFAULT_PICA_OPTS, options || {});
-                       var limiter_key = "lk_".concat(this.options.concurrency); // Share limiters to avoid multiple parallel workers when user creates
-                       // multiple pica instances.
-                       
-                       this.__limit = singletones[limiter_key] || utils.limiter(this.options.concurrency);
-                       if (!singletones[limiter_key]) singletones[limiter_key] = this.__limit; // List of supported features, according to options & browser/node.js
-                       
-                       this.features = {
-                               js: false,
-                               // pure JS implementation, can be disabled for testing
-                               wasm: false,
-                               // webassembly implementation for heavy functions
-                               cib: false,
-                               // resize via createImageBitmap (only FF at this moment)
-                               ww: false // webworkers
-                               
-                       };
-                       this.__workersPool = null; // Store requested features for webworkers
-                       
-                       this.__requested_features = [];
-                       this.__mathlib = null;
-               }
-               
-               Pica.prototype.init = function () {
-                       var _this = this;
-                       
-                       if (this.__initPromise) return this.__initPromise; // Test if we can create ImageData without canvas and memory copy
-                       
-                       if (CAN_NEW_IMAGE_DATA !== false && CAN_NEW_IMAGE_DATA !== true) {
-                               CAN_NEW_IMAGE_DATA = false;
-                               
-                               if (typeof ImageData !== 'undefined' && typeof Uint8ClampedArray !== 'undefined') {
-                                       try {
-                                               /* eslint-disable no-new */
-                                               new ImageData(new Uint8ClampedArray(400), 10, 10);
-                                               CAN_NEW_IMAGE_DATA = true;
-                                       } catch (__) {}
-                               }
-                       } // ImageBitmap can be effective in 2 places:
-                       //
-                       // 1. Threaded jpeg unpack (basic)
-                       // 2. Built-in resize (blocked due problem in chrome, see issue #89)
-                       //
-                       // For basic use we also need ImageBitmap wo support .close() method,
-                       // see https://developer.mozilla.org/ru/docs/Web/API/ImageBitmap
-                       
-                       
-                       if (CAN_CREATE_IMAGE_BITMAP !== false && CAN_CREATE_IMAGE_BITMAP !== true) {
-                               CAN_CREATE_IMAGE_BITMAP = false;
-                               
-                               if (typeof ImageBitmap !== 'undefined') {
-                                       if (ImageBitmap.prototype && ImageBitmap.prototype.close) {
-                                               CAN_CREATE_IMAGE_BITMAP = true;
-                                       } else {
-                                               this.debug('ImageBitmap does not support .close(), disabled');
-                                       }
-                               }
-                       }
-                       
-                       var features = this.options.features.slice();
-                       
-                       if (features.indexOf('all') >= 0) {
-                               features = ['cib', 'wasm', 'js', 'ww'];
-                       }
-                       
-                       this.__requested_features = features;
-                       this.__mathlib = new MathLib(features); // Check WebWorker support if requested
-                       
-                       if (features.indexOf('ww') >= 0) {
-                               if (typeof window !== 'undefined' && 'Worker' in window) {
-                                       // IE <= 11 don't allow to create webworkers from string. We should check it.
-                                       // https://connect.microsoft.com/IE/feedback/details/801810/web-workers-from-blob-urls-in-ie-10-and-11
-                                       try {
-                                               var wkr = require('webworkify')(function () {});
-                                               
-                                               wkr.terminate();
-                                               this.features.ww = true; // pool uniqueness depends on pool config + webworker config
-                                               
-                                               var wpool_key = "wp_".concat(JSON.stringify(this.options));
-                                               
-                                               if (singletones[wpool_key]) {
-                                                       this.__workersPool = singletones[wpool_key];
-                                               } else {
-                                                       this.__workersPool = new Pool(workerFabric, this.options.idle);
-                                                       singletones[wpool_key] = this.__workersPool;
-                                               }
-                                       } catch (__) {}
-                               }
-                       }
-                       
-                       var initMath = this.__mathlib.init().then(function (mathlib) {
-                               // Copy detected features
-                               assign(_this.features, mathlib.features);
-                       });
-                       
-                       var checkCibResize;
-                       
-                       if (!CAN_CREATE_IMAGE_BITMAP) {
-                               checkCibResize = Promise.resolve(false);
-                       } else {
-                               checkCibResize = utils.cib_support().then(function (status) {
-                                       if (_this.features.cib && features.indexOf('cib') < 0) {
-                                               _this.debug('createImageBitmap() resize supported, but disabled by config');
-                                               
-                                               return;
-                                       }
-                                       
-                                       if (features.indexOf('cib') >= 0) _this.features.cib = status;
-                               });
-                       } // Init math lib. That's async because can load some
-                       
-                       
-                       this.__initPromise = Promise.all([initMath, checkCibResize]).then(function () {
-                               return _this;
-                       });
-                       return this.__initPromise;
-               };
-               
-               Pica.prototype.resize = function (from, to, options) {
-                       var _this2 = this;
-                       
-                       this.debug('Start resize...');
-                       var opts = assign({}, DEFAULT_RESIZE_OPTS);
-                       
-                       if (!isNaN(options)) {
-                               opts = assign(opts, {
-                                       quality: options
-                               });
-                       } else if (options) {
-                               opts = assign(opts, options);
-                       }
-                       
-                       opts.toWidth = to.width;
-                       opts.toHeight = to.height;
-                       opts.width = from.naturalWidth || from.width;
-                       opts.height = from.naturalHeight || from.height; // Prevent stepper from infinite loop
-                       
-                       if (to.width === 0 || to.height === 0) {
-                               return Promise.reject(new Error("Invalid output size: ".concat(to.width, "x").concat(to.height)));
-                       }
-                       
-                       if (opts.unsharpRadius > 2) opts.unsharpRadius = 2;
-                       var canceled = false;
-                       var cancelToken = null;
-                       
-                       if (opts.cancelToken) {
-                               // Wrap cancelToken to avoid successive resolve & set flag
-                               cancelToken = opts.cancelToken.then(function (data) {
-                                       canceled = true;
-                                       throw data;
-                               }, function (err) {
-                                       canceled = true;
-                                       throw err;
-                               });
-                       }
-                       
-                       var DEST_TILE_BORDER = 3; // Max possible filter window size
-                       
-                       var destTileBorder = Math.ceil(Math.max(DEST_TILE_BORDER, 2.5 * opts.unsharpRadius | 0));
-                       return this.init().then(function () {
-                               if (canceled) return cancelToken; // if createImageBitmap supports resize, just do it and return
-                               
-                               if (_this2.features.cib) {
-                                       var toCtx = to.getContext('2d', {
-                                               alpha: Boolean(opts.alpha)
-                                       });
-                                       
-                                       _this2.debug('Resize via createImageBitmap()');
-                                       
-                                       return createImageBitmap(from, {
-                                               resizeWidth: opts.toWidth,
-                                               resizeHeight: opts.toHeight,
-                                               resizeQuality: utils.cib_quality_name(opts.quality)
-                                       }).then(function (imageBitmap) {
-                                               if (canceled) return cancelToken; // if no unsharp - draw directly to output canvas
-                                               
-                                               if (!opts.unsharpAmount) {
-                                                       toCtx.drawImage(imageBitmap, 0, 0);
-                                                       imageBitmap.close();
-                                                       toCtx = null;
-                                                       
-                                                       _this2.debug('Finished!');
-                                                       
-                                                       return to;
-                                               }
-                                               
-                                               _this2.debug('Unsharp result');
-                                               
-                                               var tmpCanvas = document.createElement('canvas');
-                                               tmpCanvas.width = opts.toWidth;
-                                               tmpCanvas.height = opts.toHeight;
-                                               var tmpCtx = tmpCanvas.getContext('2d', {
-                                                       alpha: Boolean(opts.alpha)
-                                               });
-                                               tmpCtx.drawImage(imageBitmap, 0, 0);
-                                               imageBitmap.close();
-                                               var iData = tmpCtx.getImageData(0, 0, opts.toWidth, opts.toHeight);
-                                               
-                                               _this2.__mathlib.unsharp(iData.data, opts.toWidth, opts.toHeight, opts.unsharpAmount, opts.unsharpRadius, opts.unsharpThreshold);
-                                               
-                                               toCtx.putImageData(iData, 0, 0);
-                                               iData = tmpCtx = tmpCanvas = toCtx = null;
-                                               
-                                               _this2.debug('Finished!');
-                                               
-                                               return to;
-                                       });
-                               } //
-                               // No easy way, let's resize manually via arrays
-                               //
-                               // Share cache between calls:
-                               //
-                               // - wasm instance
-                               // - wasm memory object
-                               //
-                               
-                               
-                               var cache = {}; // Call resizer in webworker or locally, depending on config
-                               
-                               var invokeResize = function invokeResize(opts) {
-                                       return Promise.resolve().then(function () {
-                                               if (!_this2.features.ww) return _this2.__mathlib.resizeAndUnsharp(opts, cache);
-                                               return new Promise(function (resolve, reject) {
-                                                       var w = _this2.__workersPool.acquire();
-                                                       
-                                                       if (cancelToken) cancelToken.catch(function (err) {
-                                                               return reject(err);
-                                                       });
-                                                       
-                                                       w.value.onmessage = function (ev) {
-                                                               w.release();
-                                                               if (ev.data.err) reject(ev.data.err);else resolve(ev.data.result);
-                                                       };
-                                                       
-                                                       w.value.postMessage({
-                                                               opts: opts,
-                                                               features: _this2.__requested_features,
-                                                               preload: {
-                                                                       wasm_nodule: _this2.__mathlib.__
-                                                               }
-                                                       }, [opts.src.buffer]);
-                                               });
-                                       });
-                               };
-                               
-                               var tileAndResize = function tileAndResize(from, to, opts) {
-                                       var srcCtx;
-                                       var srcImageBitmap;
-                                       var toCtx;
-                                       
-                                       var processTile = function processTile(tile) {
-                                               return _this2.__limit(function () {
-                                                       if (canceled) return cancelToken;
-                                                       var srcImageData; // Extract tile RGBA buffer, depending on input type
-                                                       
-                                                       if (utils.isCanvas(from)) {
-                                                               _this2.debug('Get tile pixel data'); // If input is Canvas - extract region data directly
-                                                               
-                                                               
-                                                               srcImageData = srcCtx.getImageData(tile.x, tile.y, tile.width, tile.height);
-                                                       } else {
-                                                               // If input is Image or decoded to ImageBitmap,
-                                                               // draw region to temporary canvas and extract data from it
-                                                               //
-                                                               // Note! Attempt to reuse this canvas causes significant slowdown in chrome
-                                                               //
-                                                               _this2.debug('Draw tile imageBitmap/image to temporary canvas');
-                                                               
-                                                               var tmpCanvas = document.createElement('canvas');
-                                                               tmpCanvas.width = tile.width;
-                                                               tmpCanvas.height = tile.height;
-                                                               var tmpCtx = tmpCanvas.getContext('2d', {
-                                                                       alpha: Boolean(opts.alpha)
-                                                               });
-                                                               tmpCtx.globalCompositeOperation = 'copy';
-                                                               tmpCtx.drawImage(srcImageBitmap || from, tile.x, tile.y, tile.width, tile.height, 0, 0, tile.width, tile.height);
-                                                               
-                                                               _this2.debug('Get tile pixel data');
-                                                               
-                                                               srcImageData = tmpCtx.getImageData(0, 0, tile.width, tile.height);
-                                                               tmpCtx = tmpCanvas = null;
-                                                       }
-                                                       
-                                                       var o = {
-                                                               src: srcImageData.data,
-                                                               width: tile.width,
-                                                               height: tile.height,
-                                                               toWidth: tile.toWidth,
-                                                               toHeight: tile.toHeight,
-                                                               scaleX: tile.scaleX,
-                                                               scaleY: tile.scaleY,
-                                                               offsetX: tile.offsetX,
-                                                               offsetY: tile.offsetY,
-                                                               quality: opts.quality,
-                                                               alpha: opts.alpha,
-                                                               unsharpAmount: opts.unsharpAmount,
-                                                               unsharpRadius: opts.unsharpRadius,
-                                                               unsharpThreshold: opts.unsharpThreshold
-                                                       };
-                                                       
-                                                       _this2.debug('Invoke resize math');
-                                                       
-                                                       return Promise.resolve().then(function () {
-                                                               return invokeResize(o);
-                                                       }).then(function (result) {
-                                                               if (canceled) return cancelToken;
-                                                               srcImageData = null;
-                                                               var toImageData;
-                                                               
-                                                               _this2.debug('Convert raw rgba tile result to ImageData');
-                                                               
-                                                               if (CAN_NEW_IMAGE_DATA) {
-                                                                       // this branch is for modern browsers
-                                                                       // If `new ImageData()` & Uint8ClampedArray suported
-                                                                       toImageData = new ImageData(new Uint8ClampedArray(result), tile.toWidth, tile.toHeight);
-                                                               } else {
-                                                                       // fallback for `node-canvas` and old browsers
-                                                                       // (IE11 has ImageData but does not support `new ImageData()`)
-                                                                       toImageData = toCtx.createImageData(tile.toWidth, tile.toHeight);
-                                                                       
-                                                                       if (toImageData.data.set) {
-                                                                               toImageData.data.set(result);
-                                                                       } else {
-                                                                               // IE9 don't have `.set()`
-                                                                               for (var i = toImageData.data.length - 1; i >= 0; i--) {
-                                                                                       toImageData.data[i] = result[i];
-                                                                               }
-                                                                       }
-                                                               }
-                                                               
-                                                               _this2.debug('Draw tile');
-                                                               
-                                                               if (NEED_SAFARI_FIX) {
-                                                                       // Safari draws thin white stripes between tiles without this fix
-                                                                       toCtx.putImageData(toImageData, tile.toX, tile.toY, tile.toInnerX - tile.toX, tile.toInnerY - tile.toY, tile.toInnerWidth + 1e-5, tile.toInnerHeight + 1e-5);
-                                                               } else {
-                                                                       toCtx.putImageData(toImageData, tile.toX, tile.toY, tile.toInnerX - tile.toX, tile.toInnerY - tile.toY, tile.toInnerWidth, tile.toInnerHeight);
-                                                               }
-                                                               
-                                                               return null;
-                                                       });
-                                               });
-                                       }; // Need to normalize data source first. It can be canvas or image.
-                                       // If image - try to decode in background if possible
-                                       
-                                       
-                                       return Promise.resolve().then(function () {
-                                               toCtx = to.getContext('2d', {
-                                                       alpha: Boolean(opts.alpha)
-                                               });
-                                               
-                                               if (utils.isCanvas(from)) {
-                                                       srcCtx = from.getContext('2d', {
-                                                               alpha: Boolean(opts.alpha)
-                                                       });
-                                                       return null;
-                                               }
-                                               
-                                               if (utils.isImage(from)) {
-                                                       // try do decode image in background for faster next operations
-                                                       if (!CAN_CREATE_IMAGE_BITMAP) return null;
-                                                       
-                                                       _this2.debug('Decode image via createImageBitmap');
-                                                       
-                                                       return createImageBitmap(from).then(function (imageBitmap) {
-                                                               srcImageBitmap = imageBitmap;
-                                                       });
-                                               }
-                                               
-                                               throw new Error('".from" should be image or canvas');
-                                       }).then(function () {
-                                               if (canceled) return cancelToken;
-                                               
-                                               _this2.debug('Calculate tiles'); //
-                                               // Here we are with "normalized" source,
-                                               // follow to tiling
-                                               //
-                                               
-                                               
-                                               var regions = createRegions({
-                                                       width: opts.width,
-                                                       height: opts.height,
-                                                       srcTileSize: _this2.options.tile,
-                                                       toWidth: opts.toWidth,
-                                                       toHeight: opts.toHeight,
-                                                       destTileBorder: destTileBorder
-                                               });
-                                               var jobs = regions.map(function (tile) {
-                                                       return processTile(tile);
-                                               });
-                                               
-                                               function cleanup() {
-                                                       if (srcImageBitmap) {
-                                                               srcImageBitmap.close();
-                                                               srcImageBitmap = null;
-                                                       }
-                                               }
-                                               
-                                               _this2.debug('Process tiles');
-                                               
-                                               return Promise.all(jobs).then(function () {
-                                                       _this2.debug('Finished!');
-                                                       
-                                                       cleanup();
-                                                       return to;
-                                               }, function (err) {
-                                                       cleanup();
-                                                       throw err;
-                                               });
-                                       });
-                               };
-                               
-                               var processStages = function processStages(stages, from, to, opts) {
-                                       if (canceled) return cancelToken;
-                                       
-                                       var _stages$shift = stages.shift(),
-                                               _stages$shift2 = _slicedToArray(_stages$shift, 2),
-                                               toWidth = _stages$shift2[0],
-                                               toHeight = _stages$shift2[1];
-                                       
-                                       var isLastStage = stages.length === 0;
-                                       opts = assign({}, opts, {
-                                               toWidth: toWidth,
-                                               toHeight: toHeight,
-                                               // only use user-defined quality for the last stage,
-                                               // use simpler (Hamming) filter for the first stages where
-                                               // scale factor is large enough (more than 2-3)
-                                               quality: isLastStage ? opts.quality : Math.min(1, opts.quality)
-                                       });
-                                       var tmpCanvas;
-                                       
-                                       if (!isLastStage) {
-                                               // create temporary canvas
-                                               tmpCanvas = document.createElement('canvas');
-                                               tmpCanvas.width = toWidth;
-                                               tmpCanvas.height = toHeight;
-                                       }
-                                       
-                                       return tileAndResize(from, isLastStage ? to : tmpCanvas, opts).then(function () {
-                                               if (isLastStage) return to;
-                                               opts.width = toWidth;
-                                               opts.height = toHeight;
-                                               return processStages(stages, tmpCanvas, to, opts);
-                                       });
-                               };
-                               
-                               var stages = createStages(opts.width, opts.height, opts.toWidth, opts.toHeight, _this2.options.tile, destTileBorder);
-                               return processStages(stages, from, to, opts);
-                       });
-               }; // RGBA buffer resize
-//
-               
-               
-               Pica.prototype.resizeBuffer = function (options) {
-                       var _this3 = this;
-                       
-                       var opts = assign({}, DEFAULT_RESIZE_OPTS, options);
-                       return this.init().then(function () {
-                               return _this3.__mathlib.resizeAndUnsharp(opts);
-                       });
-               };
-               
-               Pica.prototype.toBlob = function (canvas, mimeType, quality) {
-                       mimeType = mimeType || 'image/png';
-                       return new Promise(function (resolve) {
-                               if (canvas.toBlob) {
-                                       canvas.toBlob(function (blob) {
-                                               return resolve(blob);
-                                       }, mimeType, quality);
-                                       return;
-                               } // Fallback for old browsers
-                               
-                               
-                               var asString = atob(canvas.toDataURL(mimeType, quality).split(',')[1]);
-                               var len = asString.length;
-                               var asBuffer = new Uint8Array(len);
-                               
-                               for (var i = 0; i < len; i++) {
-                                       asBuffer[i] = asString.charCodeAt(i);
-                               }
-                               
-                               resolve(new Blob([asBuffer], {
-                                       type: mimeType
-                               }));
-                       });
-               };
-               
-               Pica.prototype.debug = function () {};
-               
-               module.exports = Pica;
-               
-       },{"./lib/mathlib":1,"./lib/pool":9,"./lib/stepper":10,"./lib/tiler":11,"./lib/utils":12,"./lib/worker":13,"object-assign":24,"webworkify":25}]},{},[])("/")
-});
-
-/**
- * This module allows resizing and conversion of HTMLImageElements to Blob and File objects
- *
- * @author     Maximilian Mader
- * @copyright  2001-2018 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Image/Resizer
- */
-define('WoltLabSuite/Core/Image/Resizer',[
-       'WoltLabSuite/Core/FileUtil',
-       'WoltLabSuite/Core/Image/ExifUtil',
-       'Pica'
-], function(FileUtil, ExifUtil, Pica) {
-       "use strict";
-       
-       var pica = new Pica({features: ['js', 'wasm', 'ww']});
-       
-       /**
-        * @constructor
-        */
-       function ImageResizer() { }
-       ImageResizer.prototype = {
-               maxWidth: 800,
-               maxHeight: 600,
-               quality: 0.8,
-               fileType: 'image/jpeg',
-               
-               /**
-                * Sets the default maximum width for this instance
-                *
-                * @param       {Number}        value   the new default maximum width
-                * @returns     {ImageResizer}          this ImageResizer instance
-                */
-               setMaxWidth: function (value) {
-                       if (value == null) value = ImageResizer.prototype.maxWidth;
-                       
-                       this.maxWidth = value;
-                       return this;
-               },
-               
-               /**
-                * Sets the default maximum height for this instance
-                *
-                * @param       {Number}        value   the new default maximum height
-                * @returns     {ImageResizer}          this ImageResizer instance
-                */
-               setMaxHeight: function (value) {
-                       if (value == null) value = ImageResizer.prototype.maxHeight;
-                       
-                       this.maxHeight = value;
-                       return this;
-               },
-               
-               /**
-                * Sets the default quality for this instance
-                *
-                * @param       {Number}        value   the new default quality
-                * @returns     {ImageResizer}          this ImageResizer instance
-                */
-               setQuality: function (value) {
-                       if (value == null) value = ImageResizer.prototype.quality;
-                       
-                       this.quality = value;
-                       return this;
-               },
-               
-               /**
-                * Sets the default file type for this instance
-                *
-                * @param       {Number}        value   the new default file type
-                * @returns     {ImageResizer}          this ImageResizer instance
-                */
-               setFileType: function (value) {
-                       if (value == null) value = ImageResizer.prototype.fileType;
-                       
-                       this.fileType = value;
-                       return this;
-               },
-               
-               /**
-                * Converts the given object of exif data and image data into a File.
-                *
-                * @param       {Object{exif: Uint8Array|undefined, image: Canvas} data  object containing exif data and image data
-                * @param       {String}        fileName        the name of the returned file
-                * @param       {String}        [fileType]      the type of the returned image
-                * @param       {Number}        [quality]       quality setting, currently only effective for "image/jpeg"
-                * @returns     {Promise<File>} the File object
-                */
-               saveFile: function (data, fileName, fileType, quality) {
-                       fileType = fileType || this.fileType;
-                       quality = quality || this.quality;
-                       
-                       var basename = fileName.match(/(.+)(\..+?)$/);
-                       
-                       return pica.toBlob(data.image, fileType, quality)
-                               .then(function (blob) {
-                                       if (fileType === 'image/jpeg' && typeof data.exif !== 'undefined') {
-                                               return ExifUtil.setExifData(blob, data.exif);
-                                       }
-                                       
-                                       return blob;
-                               })
-                               .then(function (blob) {
-                                       return FileUtil.blobToFile(blob, basename[1] + '_autoscaled');
-                               });
-               },
-               
-               /**
-                * Loads the given file into an image object and parses Exif information.
-                * 
-                * @param   {File}    file the file to load
-                * @returns {Promise} resulting image data
-                */
-               loadFile: function (file) {
-                       var exif = undefined;
-                       if (file.type === 'image/jpeg') {
-                               // Extract EXIF data
-                               exif = ExifUtil.getExifBytesFromJpeg(file);
-                       }
-                       
-                       var loader = new Promise(function (resolve, reject) {
-                               var reader = new FileReader();
-                               var image = new Image();
-                               
-                               reader.addEventListener('load', function () {
-                                       image.src = reader.result;
-                               });
-                               
-                               reader.addEventListener('error', function () {
-                                       reader.abort();
-                                       reject(reader.error);
-                               });
-                               
-                               image.addEventListener('error', reject);
-                               
-                               image.addEventListener('load', function () {
-                                       resolve(image);
-                               });
-                               
-                               reader.readAsDataURL(file);
-                       });
-                       
-                       return Promise.all([ exif, loader ])
-                               .then(function (result) {
-                                       return { exif: result[0], image: result[1] };
-                               });
-               },
-               
-               /**
-                * Downscales an image given as File object.
-                *
-                * @param       {Image}       image             the image to resize
-                * @param       {Number}      [maxWidth]        maximum width
-                * @param       {Number}      [maxHeight]       maximum height
-                * @param       {Number}      [quality]         quality in percent
-                * @param       {boolean}     [force]           whether to force scaling even if unneeded (thus re-encoding with a possibly smaller file size)
-                * @param       {Promise}     cancelPromise     a Promise used to cancel pica's operation when it resolves
-                * @returns     {Promise<Blob | undefined>}     a Promise resolving with the resized image as a {Canvas} or undefined if no resizing happened
-                */
-               resize: function (image, maxWidth, maxHeight, quality, force, cancelPromise) {
-                       maxWidth = maxWidth || this.maxWidth;
-                       maxHeight = maxHeight || this.maxHeight;
-                       quality = quality || this.quality;
-                       force = force || false;
-                       
-                       var canvas = document.createElement('canvas');
-                       
-                       // Prevent upscaling
-                       var newWidth = Math.min(maxWidth, image.width);
-                       var newHeight = Math.min(maxHeight, image.height);
-                       
-                       if (image.width <= newWidth && image.height <= newHeight && !force) {
-                               return Promise.resolve(undefined);
-                       }
-                       
-                       // Keep image ratio
-                       var ratio = Math.min(newWidth / image.width, newHeight / image.height);
-                       canvas.width = Math.floor(image.width * ratio);
-                       canvas.height = Math.floor(image.height * ratio);
-                       
-                       // Map to Pica's quality
-                       var resizeQuality = 1;
-                       if (quality >= 0.8) {
-                               resizeQuality = 3;
-                       }
-                       else if (quality >= 0.4) {
-                               resizeQuality = 2;
-                       }
-                       
-                       var options = {
-                               quality: resizeQuality,
-                               cancelToken: cancelPromise,
-                               alpha: true
-                       };
-                       
-                       return pica.resize(image, canvas, options);
-               }
-       };
-       
-       return ImageResizer;
-});
-
-/**
- * Dropdown language chooser.
- * 
- * @author     Alexander Ebert, Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Language/Chooser
- */
-define('WoltLabSuite/Core/Language/Chooser',['Dictionary', 'Language', 'Dom/Traverse', 'Dom/Util', 'ObjectMap', 'Ui/SimpleDropdown'], function(Dictionary, Language, DomTraverse, DomUtil, ObjectMap, UiSimpleDropdown) {
-       "use strict";
-       
-       var _choosers = new Dictionary();
-       var _didInit = false;
-       var _forms = new ObjectMap();
-       
-       var _callbackSubmit = null;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Language/Chooser
-        */
-       return {
-               /**
-                * Initializes a language chooser.
-                * 
-                * @param       {string}                                containerId             input element container id
-                * @param       {string}                                chooserId               input element id
-                * @param       {int}                                   languageId              selected language id
-                * @param       {object<int, object<string, string>>}   languages               data of available languages
-                * @param       {function}                              callback                function called after a language is selected
-                * @param       {boolean}                               allowEmptyValue         true if no language may be selected
-                */
-               init: function(containerId, chooserId, languageId, languages, callback, allowEmptyValue) {
-                       if (_choosers.has(chooserId)) {
-                               return;
-                       }
-                       
-                       var container = elById(containerId);
-                       if (container === null) {
-                               throw new Error("Expected a valid container id, cannot find '" + chooserId + "'.");
-                       }
-                       
-                       var element = elById(chooserId);
-                       if (element === null) {
-                               element = elCreate('input');
-                               elAttr(element, 'type', 'hidden');
-                               elAttr(element, 'id', chooserId);
-                               elAttr(element, 'name', chooserId);
-                               elAttr(element, 'value', languageId);
-                               
-                               container.appendChild(element);
-                       }
-                       
-                       this._initElement(chooserId, element, languageId, languages, callback, allowEmptyValue);
-               },
-               
-               /**
-                * Caches common event listener callbacks.
-                */
-               _setup: function() {
-                       if (_didInit) return;
-                       _didInit = true;
-                       
-                       _callbackSubmit = this._submit.bind(this);
-               },
-               
-               /**
-                * Sets up DOM and event listeners for a language chooser.
-                *
-                * @param       {string}                                chooserId               chooser id
-                * @param       {Element}                               element                 chooser element
-                * @param       {int}                                   languageId              selected language id
-                * @param       {object<int, object<string, string>>}   languages               data of available languages
-                * @param       {function}                              callback                callback function invoked on selection change
-                * @param       {boolean}                               allowEmptyValue         true if no language may be selected
-                */
-               _initElement: function(chooserId, element, languageId, languages, callback, allowEmptyValue) {
-                       var container;
-                       
-                       if (element.parentNode.nodeName === 'DD') {
-                               container = elCreate('div');
-                               container.className = 'dropdown';
-                               
-                               // language chooser is the first child so that descriptions and error messages
-                               // are always shown below the language chooser
-                               DomUtil.prepend(container, element.parentNode);
-                       }
-                       else {
-                               container = element.parentNode;
-                               container.classList.add('dropdown');
-                       }
-                       
-                       elHide(element);
-                       
-                       var dropdownToggle = elCreate('a');
-                       dropdownToggle.className = 'dropdownToggle dropdownIndicator boxFlag box24 inputPrefix' + (element.parentNode.nodeName === 'DD' ? ' button' : '');
-                       container.appendChild(dropdownToggle);
-                       
-                       var dropdownMenu = elCreate('ul');
-                       dropdownMenu.className = 'dropdownMenu';
-                       container.appendChild(dropdownMenu);
-                       
-                       var callbackClick = (function(event) {
-                               var languageId = ~~elData(event.currentTarget, 'language-id');
-                               
-                               var activeItem = DomTraverse.childByClass(dropdownMenu, 'active');
-                               if (activeItem !== null) activeItem.classList.remove('active');
-                               
-                               if (languageId) event.currentTarget.classList.add('active');
-                               
-                               this._select(chooserId, languageId, event.currentTarget);
-                       }).bind(this);
-                       
-                       // add language dropdown items
-                       var link, img, listItem, span;
-                       for (var availableLanguageId in languages) {
-                               if (languages.hasOwnProperty(availableLanguageId)) {
-                                       var language = languages[availableLanguageId];
-                                       
-                                       listItem = elCreate('li');
-                                       listItem.className = 'boxFlag';
-                                       listItem.addEventListener(WCF_CLICK_EVENT, callbackClick);
-                                       elData(listItem, 'language-id', availableLanguageId);
-                                       if (language.languageCode !== undefined) elData(listItem, 'language-code', language.languageCode);
-                                       dropdownMenu.appendChild(listItem);
-                                       
-                                       link = elCreate('a');
-                                       link.className = 'box24';
-                                       listItem.appendChild(link);
-                                       
-                                       img = elCreate('img');
-                                       elAttr(img, 'src', language.iconPath);
-                                       elAttr(img, 'alt', '');
-                                       img.className = 'iconFlag';
-                                       link.appendChild(img);
-                                       
-                                       span = elCreate('span');
-                                       span.textContent = language.languageName;
-                                       link.appendChild(span);
-                                       
-                                       if (availableLanguageId == languageId) {
-                                               dropdownToggle.innerHTML = listItem.firstChild.innerHTML;
-                                       }
-                               }
-                       }
-                       
-                       // add dropdown item for "no selection"
-                       if (allowEmptyValue) {
-                               listItem = elCreate('li');
-                               listItem.className = 'dropdownDivider';
-                               dropdownMenu.appendChild(listItem);
-                               
-                               listItem = elCreate('li');
-                               elData(listItem, 'language-id', 0);
-                               listItem.addEventListener(WCF_CLICK_EVENT, callbackClick);
-                               dropdownMenu.appendChild(listItem);
-                               
-                               link = elCreate('a');
-                               link.textContent = Language.get('wcf.global.language.noSelection');
-                               listItem.appendChild(link);
-                               
-                               if (languageId === 0) {
-                                       dropdownToggle.innerHTML = listItem.firstChild.innerHTML;
-                               }
-                               
-                               listItem.addEventListener(WCF_CLICK_EVENT, callbackClick);
-                       }
-                       else if (languageId === 0) {
-                               dropdownToggle.innerHTML = null;
-                               
-                               var div = elCreate('div');
-                               dropdownToggle.appendChild(div);
-                               
-                               span = elCreate('span');
-                               span.className = 'icon icon24 fa-question';
-                               div.appendChild(span);
-                               
-                               span = elCreate('span');
-                               span.textContent = Language.get('wcf.global.language.noSelection');
-                               div.appendChild(span);
-                       }
-                       
-                       UiSimpleDropdown.init(dropdownToggle);
-                       
-                       _choosers.set(chooserId, {
-                               callback: callback,
-                               dropdownMenu: dropdownMenu,
-                               dropdownToggle: dropdownToggle,
-                               element: element
-                       });
-                       
-                       // bind to submit event
-                       var form = DomTraverse.parentByTag(element, 'FORM');
-                       if (form !== null) {
-                               form.addEventListener('submit', _callbackSubmit);
-                               
-                               var chooserIds = _forms.get(form);
-                               if (chooserIds === undefined) {
-                                       chooserIds = [];
-                                       _forms.set(form, chooserIds);
-                               }
-                               
-                               chooserIds.push(chooserId);
-                       }
-               },
-               
-               /**
-                * Selects a language from the dropdown list.
-                * 
-                * @param       {string}        chooserId       input element id
-                * @param       {int}           languageId      language id or `0` to disable i18n
-                * @param       {Element=}      listItem        selected list item
-                */
-               _select: function(chooserId, languageId, listItem) {
-                       var chooser = _choosers.get(chooserId);
-                       
-                       if (listItem === undefined) {
-                               var listItems = chooser.dropdownMenu.childNodes;
-                               for (var i = 0, length = listItems.length; i < length; i++) {
-                                       var _listItem = listItems[i];
-                                       if (~~elData(_listItem, 'language-id') === languageId) {
-                                               listItem = _listItem;
-                                               break;
-                                       }
-                               }
-                               
-                               if (listItem === undefined) {
-                                       throw new Error("Cannot select unknown language id '" + languageId + "'");
-                               }
-                       }
-                       
-                       chooser.element.value = languageId;
-                       
-                       chooser.dropdownToggle.innerHTML = listItem.firstChild.innerHTML;
-                       
-                       _choosers.set(chooserId, chooser);
-                       
-                       // execute callback
-                       if (typeof chooser.callback === 'function') {
-                               chooser.callback(listItem);
-                       }
-               },
-               
-               /**
-                * Inserts hidden fields for the language chooser value on submit.
-                *
-                * @param       {object}        event           event object
-                */
-               _submit: function(event) {
-                       var elementIds = _forms.get(event.currentTarget);
-                       
-                       var input;
-                       for (var i = 0, length = elementIds.length; i < length; i++) {
-                               input = elCreate('input');
-                               input.type = 'hidden';
-                               input.name = elementIds[i];
-                               input.value = this.getLanguageId(elementIds[i]);
-                               
-                               event.currentTarget.appendChild(input);
-                       }
-               },
-               
-               /**
-                * Returns the chooser for an input field.
-                * 
-                * @param       {string}        chooserId       input element id
-                * @return      {Dictionary}    data of the chooser
-                */
-               getChooser: function(chooserId) {
-                       var chooser = _choosers.get(chooserId);
-                       if (chooser === undefined) {
-                               throw new Error("Expected a valid language chooser input element, '" + chooserId + "' is not i18n input field.");
-                       }
-                       
-                       return chooser;
-               },
-               
-               /**
-                * Returns the selected language for a certain chooser.
-                * 
-                * @param       {string}        chooserId       input element id
-                * @return      {int}           chosen language id
-                */
-               getLanguageId: function(chooserId) {
-                       return ~~this.getChooser(chooserId).element.value;
-               },
-               
-               /**
-                * Removes the chooser with given id.
-                * 
-                * @param       {string}        chooserId       input element id
-                */
-               removeChooser: function(chooserId) {
-                       if (_choosers.has(chooserId)) {
-                               _choosers.delete(chooserId);
-                       }
-               },
-               
-               /**
-                * Sets the language for a certain chooser.
-                * 
-                * @param       {string}        chooserId       input element id
-                * @param       {int}           languageId      language id to be set
-                */
-               setLanguageId: function(chooserId, languageId) {
-                       if (_choosers.get(chooserId) === undefined) {
-                               throw new Error("Expected a valid  input element, '" + chooserId + "' is not i18n input field.");
-                       }
-                       
-                       this._select(chooserId, languageId);
-               }
-       };
-});
-
-/**
- * I18n interface for input and textarea fields.
- * 
- * @author      Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module      WoltLabSuite/Core/Language/Input
- */
-define('WoltLabSuite/Core/Language/Input',['Core', 'Dictionary', 'Language', 'ObjectMap', 'StringUtil', 'Dom/Traverse', 'Dom/Util', 'Ui/SimpleDropdown'], function(Core, Dictionary, Language, ObjectMap, StringUtil, DomTraverse, DomUtil, UiSimpleDropdown) {
-       "use strict";
-       
-       var _elements = new Dictionary();
-       var _didInit = false;
-       var _forms = new ObjectMap();
-       var _values = new Dictionary();
-       
-       var _callbackDropdownToggle = null;
-       var _callbackSubmit = null;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Language/Input
-        */
-       return {
-               /**
-                * Initializes an input field.
-                * 
-                * @param       {string}        elementId               input element id
-                * @param       {Object}        values                  preset values per language id
-                * @param       {Object}        availableLanguages      language names per language id
-                * @param       {boolean}       forceSelection          require i18n input
-                */
-               init: function(elementId, values, availableLanguages, forceSelection) {
-                       if (_values.has(elementId)) {
-                               return;
-                       }
-                       
-                       var element = elById(elementId);
-                       if (element === null) {
-                               throw new Error("Expected a valid element id, cannot find '" + elementId + "'.");
-                       }
-                       
-                       this._setup();
-                       
-                       // unescape values
-                       var unescapedValues = new Dictionary();
-                       for (var key in values) {
-                               if (values.hasOwnProperty(key)) {
-                                       unescapedValues.set(~~key, StringUtil.unescapeHTML(values[key]));
-                               }
-                       }
-                       
-                       _values.set(elementId, unescapedValues);
-                       
-                       this._initElement(elementId, element, unescapedValues, availableLanguages, forceSelection);
-               },
-               
-               /**
-                * Registers a callback for an element.
-                * 
-                * @param       {string}        elementId
-                * @param       {string}        eventName
-                * @param       {function}      callback
-                */
-               registerCallback: function (elementId, eventName, callback) {
-                       if (!_values.has(elementId)) {
-                               throw new Error("Unknown element id '" + elementId + "'.");
-                       }
-                       
-                       _elements.get(elementId).callbacks.set(eventName, callback);
-               },
-               
-               /**
-                * Unregisters the element with the given id.
-                * 
-                * @param       {string}        elementId
-                * @since       5.2
-                */
-               unregister: function(elementId) {
-                       if (!_values.has(elementId)) {
-                               throw new Error("Unknown element id '" + elementId + "'.");
-                       }
-                       
-                       _values.delete(elementId);
-                       _elements.delete(elementId);
-               },
-               
-               /**
-                * Caches common event listener callbacks.
-                */
-               _setup: function() {
-                       if (_didInit) return;
-                       _didInit = true;
-                       
-                       _callbackDropdownToggle = this._dropdownToggle.bind(this);
-                       _callbackSubmit = this._submit.bind(this);
-               },
-               
-               /**
-                * Sets up DOM and event listeners for an input field.
-                * 
-                * @param       {string}        elementId               input element id
-                * @param       {Element}       element                 input or textarea element
-                * @param       {Dictionary}    values                  preset values per language id
-                * @param       {Object}        availableLanguages      language names per language id
-                * @param       {boolean}       forceSelection          require i18n input
-                */
-               _initElement: function(elementId, element, values, availableLanguages, forceSelection) {
-                       var container = element.parentNode;
-                       if (!container.classList.contains('inputAddon')) {
-                               container = elCreate('div');
-                               container.className = 'inputAddon' + (element.nodeName === 'TEXTAREA' ? ' inputAddonTextarea' : '');
-                               //noinspection JSCheckFunctionSignatures
-                               elData(container, 'input-id', elementId);
-                               
-                               var hasFocus = document.activeElement === element;
-                               
-                               // DOM manipulation causes focused element to lose focus
-                               element.parentNode.insertBefore(container, element);
-                               container.appendChild(element);
-                               
-                               if (hasFocus) {
-                                       element.focus();
-                               }
-                       }
-                       
-                       container.classList.add('dropdown');
-                       var button = elCreate('span');
-                       button.className = 'button dropdownToggle inputPrefix';
-                       
-                       var span = elCreate('span');
-                       span.textContent = Language.get('wcf.global.button.disabledI18n');
-                       
-                       button.appendChild(span);
-                       container.insertBefore(button, element);
-                       
-                       var dropdownMenu = elCreate('ul');
-                       dropdownMenu.className = 'dropdownMenu';
-                       DomUtil.insertAfter(dropdownMenu, button);
-                       
-                       var callbackClick = (function(event, isInit) {
-                               var languageId = ~~elData(event.currentTarget, 'language-id');
-                               
-                               var activeItem = DomTraverse.childByClass(dropdownMenu, 'active');
-                               if (activeItem !== null) activeItem.classList.remove('active');
-                               
-                               if (languageId) event.currentTarget.classList.add('active');
-                               
-                               this._select(elementId, languageId, isInit || false);
-                       }).bind(this);
-                       
-                       // build language dropdown
-                       var listItem;
-                       for (var languageId in availableLanguages) {
-                               if (availableLanguages.hasOwnProperty(languageId)) {
-                                       listItem = elCreate('li');
-                                       elData(listItem, 'language-id', languageId);
-                                       
-                                       span = elCreate('span');
-                                       span.textContent = availableLanguages[languageId];
-                                       
-                                       listItem.appendChild(span);
-                                       listItem.addEventListener(WCF_CLICK_EVENT, callbackClick);
-                                       dropdownMenu.appendChild(listItem);
-                               }
-                       }
-                       
-                       if (forceSelection !== true) {
-                               listItem = elCreate('li');
-                               listItem.className = 'dropdownDivider';
-                               dropdownMenu.appendChild(listItem);
-                               
-                               listItem = elCreate('li');
-                               elData(listItem, 'language-id', 0);
-                               span = elCreate('span');
-                               span.textContent = Language.get('wcf.global.button.disabledI18n');
-                               listItem.appendChild(span);
-                               listItem.addEventListener(WCF_CLICK_EVENT, callbackClick);
-                               dropdownMenu.appendChild(listItem);
-                       }
-                       
-                       var activeItem = null;
-                       if (forceSelection === true || values.size) {
-                               for (var i = 0, length = dropdownMenu.childElementCount; i < length; i++) {
-                                       //noinspection JSUnresolvedVariable
-                                       if (~~elData(dropdownMenu.children[i], 'language-id') === LANGUAGE_ID) {
-                                               activeItem = dropdownMenu.children[i];
-                                               break;
-                                       }
-                               }
-                       }
-                       
-                       UiSimpleDropdown.init(button);
-                       UiSimpleDropdown.registerCallback(container.id, _callbackDropdownToggle);
-                       
-                       _elements.set(elementId, {
-                               buttonLabel: button.children[0],
-                               callbacks: new Dictionary(),
-                               element: element,
-                               languageId: 0,
-                               isEnabled: true,
-                               forceSelection: forceSelection
-                       });
-                       
-                       // bind to submit event
-                       var submit = DomTraverse.parentByTag(element, 'FORM');
-                       if (submit !== null) {
-                               submit.addEventListener('submit', _callbackSubmit);
-                               
-                               var elementIds = _forms.get(submit);
-                               if (elementIds === undefined) {
-                                       elementIds = [];
-                                       _forms.set(submit, elementIds);
-                               }
-                               
-                               elementIds.push(elementId);
-                       }
-                       
-                       if (activeItem !== null) {
-                               callbackClick({ currentTarget: activeItem }, true);
-                       }
-               },
-               
-               /**
-                * Selects a language or non-i18n from the dropdown list.
-                * 
-                * @param       {string}        elementId       input element id
-                * @param       {int}           languageId      language id or `0` to disable i18n
-                * @param       {boolean}       isInit          triggers pre-selection on init
-                */
-               _select: function(elementId, languageId, isInit) {
-                       var data = _elements.get(elementId);
-                       
-                       var dropdownMenu = UiSimpleDropdown.getDropdownMenu(data.element.closest('.inputAddon').id);
-                       var item, label = '';
-                       for (var i = 0, length = dropdownMenu.childElementCount; i < length; i++) {
-                               item = dropdownMenu.children[i];
-                               
-                               var itemLanguageId = elData(item, 'language-id');
-                               if (itemLanguageId.length && languageId === ~~itemLanguageId) {
-                                       label = item.children[0].textContent;
-                               }
-                       }
-                       
-                       // save current value
-                       if (data.languageId !== languageId) {
-                               var values = _values.get(elementId);
-                               
-                               if (data.languageId) {
-                                       values.set(data.languageId, data.element.value);
-                               }
-                               
-                               if (languageId === 0) {
-                                       _values.set(elementId, new Dictionary());
-                               }
-                               else if (data.buttonLabel.classList.contains('active') || isInit === true) {
-                                       data.element.value = (values.has(languageId)) ? values.get(languageId) : '';
-                               }
-                               
-                               // update label
-                               data.buttonLabel.textContent = label;
-                               data.buttonLabel.classList[(languageId ? 'add' : 'remove')]('active');
-                               
-                               data.languageId = languageId;
-                       }
-                       
-                       if (!isInit) {
-                               data.element.blur();
-                               data.element.focus();
-                       }
-                       
-                       if (data.callbacks.has('select')) {
-                               data.callbacks.get('select')(data.element);
-                       }
-               },
-               
-               /**
-                * Callback for dropdowns being opened, flags items with a missing value for one or more languages.
-                * 
-                * @param       {string}        containerId     dropdown container id
-                * @param       {string}        action          toggle action, can be `open` or `close`
-                */
-               _dropdownToggle: function(containerId, action) {
-                       if (action !== 'open') {
-                               return;
-                       }
-                       
-                       var dropdownMenu = UiSimpleDropdown.getDropdownMenu(containerId);
-                       var elementId = elData(elById(containerId), 'input-id');
-                       var data = _elements.get(elementId);
-                       var values = _values.get(elementId);
-                       
-                       var item, languageId;
-                       for (var i = 0, length = dropdownMenu.childElementCount; i < length; i++) {
-                               item = dropdownMenu.children[i];
-                               languageId = ~~elData(item, 'language-id');
-                               
-                               if (languageId) {
-                                       var hasMissingValue = false;
-                                       if (data.languageId) {
-                                               if (languageId === data.languageId) {
-                                                       hasMissingValue = (data.element.value.trim() === '');
-                                               }
-                                               else {
-                                                       hasMissingValue = (!values.get(languageId));
-                                               }
-                                       }
-                                       
-                                       item.classList[(hasMissingValue ? 'add' : 'remove')]('missingValue');
-                               }
-                       }
-               },
-               
-               /**
-                * Inserts hidden fields for i18n input on submit.
-                * 
-                * @param       {Object}        event           event object
-                */
-               _submit: function(event) {
-                       var elementIds = _forms.get(event.currentTarget);
-                       
-                       var data, elementId, input, values;
-                       for (var i = 0, length = elementIds.length; i < length; i++) {
-                               elementId = elementIds[i];
-                               data = _elements.get(elementId);
-                               if (data.isEnabled) {
-                                       values = _values.get(elementId);
-                                       
-                                       if (data.callbacks.has('submit')) {
-                                               data.callbacks.get('submit')(data.element);
-                                       }
-                                       
-                                       // update with current value
-                                       if (data.languageId) {
-                                               values.set(data.languageId, data.element.value);
-                                       }
-                                       
-                                       if (values.size) {
-                                               values.forEach(function(value, languageId) {
-                                                       input = elCreate('input');
-                                                       input.type = 'hidden';
-                                                       input.name = elementId + '_i18n[' + languageId + ']';
-                                                       input.value = value;
-                                                       
-                                                       event.currentTarget.appendChild(input);
-                                               });
-                                               
-                                               // remove name attribute to enforce i18n values
-                                               data.element.removeAttribute('name');
-                                       }
-                               }
-                       }
-               },
-               
-               /**
-                * Returns the values of an input field.
-                * 
-                * @param       {string}        elementId       input element id
-                * @return      {Dictionary}    values stored for the different languages
-                */
-               getValues: function(elementId) {
-                       var element = _elements.get(elementId);
-                       if (element === undefined) {
-                               throw new Error("Expected a valid i18n input element, '" + elementId + "' is not i18n input field.");
-                       }
-                       
-                       var values = _values.get(elementId);
-                       
-                       // update with current value
-                       values.set(element.languageId, element.element.value);
-                       
-                       return values;
-               },
-               
-               /**
-                * Sets the values of an input field.
-                * 
-                * @param       {string}        elementId       input element id
-                * @param       {Dictionary}    values          values for the different languages
-                */
-               setValues: function(elementId, values) {
-                       var element = _elements.get(elementId);
-                       if (element === undefined) {
-                               throw new Error("Expected a valid i18n input element, '" + elementId + "' is not i18n input field.");
-                       }
-                       
-                       if (Core.isPlainObject(values)) {
-                               values = Dictionary.fromObject(values);
-                       }
-                       
-                       element.element.value = '';
-                       
-                       if (values.has(0)) {
-                               element.element.value = values.get(0);
-                               values['delete'](0);
-                               _values.set(elementId, values);
-                               this._select(elementId, 0, true);
-                               return;
-                       }
-                       
-                       _values.set(elementId, values);
-                       
-                       element.languageId = 0;
-                       //noinspection JSUnresolvedVariable
-                       this._select(elementId, LANGUAGE_ID, true);
-               },
-               
-               /**
-                * Disables the i18n interface for an input field.
-                * 
-                * @param       {string}        elementId       input element id
-                */
-               disable: function(elementId) {
-                       var element = _elements.get(elementId);
-                       if (element === undefined) {
-                               throw new Error("Expected a valid element, '" + elementId + "' is not an i18n input field.");
-                       }
-                       
-                       if (!element.isEnabled) return;
-                       
-                       element.isEnabled = false;
-                       
-                       // hide language dropdown
-                       //noinspection JSCheckFunctionSignatures
-                       elHide(element.buttonLabel.parentNode);
-                       var dropdownContainer = element.buttonLabel.parentNode.parentNode;
-                       dropdownContainer.classList.remove('inputAddon');
-                       dropdownContainer.classList.remove('dropdown');
-               },
-               
-               /**
-                * Enables the i18n interface for an input field.
-                * 
-                * @param       {string}        elementId       input element id
-                */
-               enable: function(elementId) {
-                       var element = _elements.get(elementId);
-                       if (element === undefined) {
-                               throw new Error("Expected a valid i18n input element, '" + elementId + "' is not i18n input field.");
-                       }
-                       
-                       if (element.isEnabled) return;
-                       
-                       element.isEnabled = true;
-                       
-                       // show language dropdown
-                       //noinspection JSCheckFunctionSignatures
-                       elShow(element.buttonLabel.parentNode);
-                       var dropdownContainer = element.buttonLabel.parentNode.parentNode;
-                       dropdownContainer.classList.add('inputAddon');
-                       dropdownContainer.classList.add('dropdown');
-               },
-               
-               /**
-                * Returns true if i18n input is enabled for an input field.
-                * 
-                * @param       {string}        elementId       input element id
-                * @return      {boolean}
-                */
-               isEnabled: function(elementId) {
-                       var element = _elements.get(elementId);
-                       if (element === undefined) {
-                               throw new Error("Expected a valid i18n input element, '" + elementId + "' is not i18n input field.");
-                       }
-                       
-                       return element.isEnabled;
-               },
-               
-               /**
-                * Returns true if the value of an i18n input field is valid.
-                * 
-                * If the element is disabled, true is returned.
-                * 
-                * @param       {string}        elementId               input element id
-                * @param       {boolean}       permitEmptyValue        if true, input may be empty for all languages
-                * @return      {boolean}       true if input is valid
-                */
-               validate: function(elementId, permitEmptyValue) {
-                       var element = _elements.get(elementId);
-                       if (element === undefined) {
-                               throw new Error("Expected a valid i18n input element, '" + elementId + "' is not i18n input field.");
-                       }
-                       
-                       if (!element.isEnabled) return true;
-                       
-                       var values = _values.get(elementId);
-                       
-                       var dropdownMenu = UiSimpleDropdown.getDropdownMenu(element.element.parentNode.id);
-                       
-                       if (element.languageId) {
-                               values.set(element.languageId, element.element.value);
-                       }
-                       
-                       var item, languageId;
-                       var hasEmptyValue = false, hasNonEmptyValue = false;
-                       for (var i = 0, length = dropdownMenu.childElementCount; i < length; i++) {
-                               item = dropdownMenu.children[i];
-                               languageId = ~~elData(item, 'language-id');
-                               
-                               if (languageId) {
-                                       if (!values.has(languageId) || values.get(languageId).length === 0) {
-                                               // input has non-empty value for previously checked language
-                                               if (hasNonEmptyValue) {
-                                                       return false;
-                                               }
-                                               
-                                               hasEmptyValue = true;
-                                       }
-                                       else {
-                                               // input has empty value for previously checked language
-                                               if (hasEmptyValue) {
-                                                       return false;
-                                               }
-                                               
-                                               hasNonEmptyValue = true;
-                                       }
-                               }
-                       }
-                       
-                       return (!hasEmptyValue || permitEmptyValue);
-               }
-       };
-});
-
-/**
- * I18n interface for wysiwyg input fields.
- * 
- * @author      Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module      WoltLabSuite/Core/Language/Text
- */
-define('WoltLabSuite/Core/Language/Text',['Core', './Input'], function (Core, LanguageInput) {
-       "use strict";
-       
-       /**
-        * @exports     WoltLabSuite/Core/Language/Text
-        */
-       return {
-               /**
-                * Initializes an WYSIWYG input field.
-                * 
-                * @param       {string}        elementId               input element id
-                * @param       {Object}        values                  preset values per language id
-                * @param       {Object}        availableLanguages      language names per language id
-                * @param       {boolean}       forceSelection          require i18n input
-                */
-               init: function(elementId, values, availableLanguages, forceSelection) {
-                       var element = elById(elementId);
-                       if (!element || element.nodeName !== 'TEXTAREA' || !element.classList.contains('wysiwygTextarea')) {
-                               throw new Error("Expected <textarea class=\"wysiwygTextarea\" /> for id '" + elementId + "'.");
-                       }
-                       
-                       LanguageInput.init(elementId, values, availableLanguages, forceSelection);
-                       
-                       //noinspection JSUnresolvedFunction
-                       LanguageInput.registerCallback(elementId, 'select', this._callbackSelect.bind(this));
-                       //noinspection JSUnresolvedFunction
-                       LanguageInput.registerCallback(elementId, 'submit', this._callbackSubmit.bind(this));
-               },
-               
-               /**
-                * Refreshes the editor content on language switch.
-                * 
-                * @param       {Element}       element         input element
-                * @protected
-                */
-               _callbackSelect: function (element) {
-                       if (window.jQuery !== undefined) {
-                               window.jQuery(element).redactor('code.set', element.value);
-                       }
-               },
-               
-               /**
-                * Refreshes the input element value on submit.
-                * 
-                * @param       {Element}       element         input element
-                * @protected
-                */
-               _callbackSubmit: function (element) {
-                       if (window.jQuery !== undefined) {
-                               element.value = window.jQuery(element).redactor('code.get');
-                       }
-               }
-       }
-});
-
-/**
- * Handles editing media files via dialog.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Media/Editor
- */
-define(
-       'WoltLabSuite/Core/Media/Editor',[
-               'Ajax',                         'Core',                       'Dictionary',          'Dom/ChangeListener',
-               'Dom/Traverse',                 'Language',                   'Ui/Dialog',           'Ui/Notification',
-               'WoltLabSuite/Core/Language/Chooser', 'WoltLabSuite/Core/Language/Input', 'EventKey'
-       ],
-       function(
-               Ajax,                            Core,                         Dictionary,            DomChangeListener,
-               DomTraverse,                     Language,                     UiDialog,              UiNotification,
-               LanguageChooser,                 LanguageInput,                EventKey
-       )
-{
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       _ajaxSetup: function() {},
-                       _ajaxSuccess: function() {},
-                       _close: function() {},
-                       _keyPress: function() {},
-                       _saveData: function() {},
-                       _updateLanguageFields: function() {},
-                       edit: function() {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * @constructor
-        */
-       function MediaEditor(callbackObject) {
-               this._callbackObject = callbackObject || {};
-               
-               if (this._callbackObject._editorClose && typeof this._callbackObject._editorClose !== 'function') {
-                       throw new TypeError("Callback object has no function '_editorClose'.");
-               }
-               if (this._callbackObject._editorSuccess && typeof this._callbackObject._editorSuccess !== 'function') {
-                       throw new TypeError("Callback object has no function '_editorSuccess'.");
-               }
-               
-               this._media = null;
-               this._availableLanguageCount = 1;
-               this._categoryIds = [];
-               this._oldCategoryId = 0;
-               
-               this._dialogs = new Dictionary();
-       }
-       MediaEditor.prototype = {
-               /**
-                * Returns the data for Ajax to setup the Ajax/Request object.
-                * 
-                * @return      {object}        setup data for Ajax/Request object
-                */
-               _ajaxSetup: function() {
-                       return {
-                               data: {
-                                       actionName: 'update',
-                                       className: 'wcf\\data\\media\\MediaAction'
-                               }
-                       };
-               },
-               
-               /**
-                * Handles successful AJAX requests.
-                * 
-                * @param       {object}        data    response data
-                */
-               _ajaxSuccess: function(data) {
-                       UiNotification.show();
-                       
-                       if (this._callbackObject._editorSuccess) {
-                               this._callbackObject._editorSuccess(this._media, this._oldCategoryId);
-                               this._oldCategoryId = 0;
-                       }
-                       
-                       UiDialog.close('mediaEditor_' + this._media.mediaID);
-                       
-                       this._media = null;
-               },
-               
-               /**
-                * Is called if an editor is manually closed by the user.
-                */
-               _close: function() {
-                       this._media = null;
-                       
-                       if (this._callbackObject._editorClose) {
-                               this._callbackObject._editorClose();
-                       }
-               },
-               
-               /**
-                * Handles the `[ENTER]` key to submit the form.
-                * 
-                * @param       {object}        event           event object
-                */
-               _keyPress: function(event) {
-                       if (EventKey.Enter(event)) {
-                               event.preventDefault();
-                               
-                               this._saveData();
-                       }
-               },
-               
-               /**
-                * Saves the data of the currently edited media.
-                */
-               _saveData: function() {
-                       var content = UiDialog.getDialog('mediaEditor_' + this._media.mediaID).content;
-                       
-                       var categoryId = elBySel('select[name=categoryID]', content);
-                       var altText = elBySel('input[name=altText]', content);
-                       var caption = elBySel('textarea[name=caption]', content);
-                       var captionEnableHtml = elBySel('input[name=captionEnableHtml]', content);
-                       var title = elBySel('input[name=title]', content);
-                       
-                       var hasError = false;
-                       var altTextError = (altText ? DomTraverse.childByClass(altText.parentNode.parentNode, 'innerError') : false);
-                       var captionError = (caption ? DomTraverse.childByClass(caption.parentNode.parentNode, 'innerError') : false);
-                       var titleError = DomTraverse.childByClass(title.parentNode.parentNode, 'innerError');
-                       
-                       // category
-                       this._oldCategoryId = this._media.categoryID;
-                       if (this._categoryIds.length) {
-                               this._media.categoryID = ~~categoryId.value;
-                               
-                               // if the selected category id not valid (manipulated DOM), ignore
-                               if (this._categoryIds.indexOf(this._media.categoryID) === -1) {
-                                       this._media.categoryID = 0;
-                               }
-                       }
-                       
-                       // language and multilingualism
-                       if (this._availableLanguageCount > 1) {
-                               this._media.isMultilingual = ~~elBySel('input[name=isMultilingual]', content).checked;
-                               this._media.languageID = this._media.isMultilingual ? null : LanguageChooser.getLanguageId('mediaEditor_' + this._media.mediaID + '_languageID');
-                       }
-                       else {
-                               this._media.languageID = LANGUAGE_ID;
-                       }
-                       
-                       // altText, caption and title
-                       this._media.altText = {};
-                       this._media.caption = {};
-                       this._media.title = {};
-                       if (this._availableLanguageCount > 1 && this._media.isMultilingual) {
-                               if (elById('altText_' + this._media.mediaID) && !LanguageInput.validate('altText_' + this._media.mediaID, true)) {
-                                       hasError = true;
-                                       if (!altTextError) {
-                                               var error = elCreate('small');
-                                               error.className = 'innerError';
-                                               error.textContent = Language.get('wcf.global.form.error.multilingual');
-                                               altText.parentNode.parentNode.appendChild(error);
-                                       }
-                               }
-                               if (elById('caption_' + this._media.mediaID) && !LanguageInput.validate('caption_' + this._media.mediaID, true)) {
-                                       hasError = true;
-                                       if (!captionError) {
-                                               var error = elCreate('small');
-                                               error.className = 'innerError';
-                                               error.textContent = Language.get('wcf.global.form.error.multilingual');
-                                               caption.parentNode.parentNode.appendChild(error);
-                                       }
-                               }
-                               if (!LanguageInput.validate('title_' + this._media.mediaID, true)) {
-                                       hasError = true;
-                                       if (!titleError) {
-                                               var error = elCreate('small');
-                                               error.className = 'innerError';
-                                               error.textContent = Language.get('wcf.global.form.error.multilingual');
-                                               title.parentNode.parentNode.appendChild(error);
-                                       }
-                               }
-                               
-                               this._media.altText = (elById('altText_' + this._media.mediaID) ? LanguageInput.getValues('altText_' + this._media.mediaID).toObject() : '');
-                               this._media.caption = (elById('caption_' + this._media.mediaID) ? LanguageInput.getValues('caption_' + this._media.mediaID).toObject() : '');
-                               this._media.title = LanguageInput.getValues('title_' + this._media.mediaID).toObject();
-                       }
-                       else {
-                               this._media.altText[this._media.languageID] = (altText ? altText.value : '');
-                               this._media.caption[this._media.languageID] = (caption ? caption.value : '');
-                               this._media.title[this._media.languageID] = title.value;
-                       }
-                       
-                       // captionEnableHtml
-                       this._media.captionEnableHtml = ~~elBySel('input[name=captionEnableHtml]', content).checked;
-                       
-                       var aclValues = {
-                               allowAll: ~~elById('mediaEditor_' + this._media.mediaID + '_aclAllowAll').checked,
-                               group: [],
-                               user: []
-                       };
-                       
-                       var aclGroups = elBySelAll('input[name="aclValues[group][]"]', content);
-                       for (var i = 0, length = aclGroups.length; i < length; i++) {
-                               aclValues.group.push(~~aclGroups[i].value);
-                       }
-                       
-                       var aclUsers = elBySelAll('input[name="aclValues[user][]"]', content);
-                       for (var i = 0, length = aclUsers.length; i < length; i++) {
-                               aclValues.user.push(~~aclUsers[i].value);
-                       }
-                       
-                       if (!hasError) {
-                               if (altTextError) elRemove(altTextError);
-                               if (captionError) elRemove(captionError);
-                               if (titleError) elRemove(titleError);
-                               
-                               Ajax.api(this, {
-                                       actionName: 'update',
-                                       objectIDs: [ this._media.mediaID ],
-                                       parameters: {
-                                               aclValues: aclValues,
-                                               altText: this._media.altText,
-                                               caption: this._media.caption,
-                                               data: {
-                                                       captionEnableHtml: this._media.captionEnableHtml,
-                                                       categoryID: this._media.categoryID,
-                                                       isMultilingual: this._media.isMultilingual,
-                                                       languageID: this._media.languageID
-                                               },
-                                               title: this._media.title
-                                       }
-                               });
-                       }
-               },
-               
-               /**
-                * Updates language-related input fields depending on whether multilingualism
-                * is enabled.
-                */
-               _updateLanguageFields: function(event, element) {
-                       if (event) element = event.currentTarget;
-                       
-                       var languageChooserContainer = elById('mediaEditor_' + this._media.mediaID + '_languageIDContainer').parentNode;
-                       
-                       if (element.checked) {
-                               LanguageInput.enable('title_' + this._media.mediaID);
-                               if (elById('caption_' + this._media.mediaID)) LanguageInput.enable('caption_' + this._media.mediaID);
-                               if (elById('altText_' + this._media.mediaID)) LanguageInput.enable('altText_' + this._media.mediaID);
-                               
-                               elHide(languageChooserContainer);
-                       }
-                       else {
-                               LanguageInput.disable('title_' + this._media.mediaID);
-                               if (elById('caption_' + this._media.mediaID)) LanguageInput.disable('caption_' + this._media.mediaID);
-                               if (elById('altText_' + this._media.mediaID)) LanguageInput.disable('altText_' + this._media.mediaID);
-                               
-                               elShow(languageChooserContainer);
-                       }
-               },
-               
-               /**
-                * Edits the media with the given data.
-                * 
-                * @param       {object|integer}        media           data of the edited media or media id for which the data will be loaded
-                */
-               edit: function(media) {
-                       if (typeof media !== 'object') {
-                               media = {
-                                       mediaID: ~~media
-                               };
-                       }
-                       
-                       if (this._media !== null) {
-                               throw new Error("Cannot edit media with id '" + media.mediaID + "' while editing media with id '" + this._media.mediaID + "'");
-                       }
-                       
-                       this._media = media;
-                       
-                       if (!this._dialogs.has('mediaEditor_' + media.mediaID)) {
-                               this._dialogs.set('mediaEditor_' + media.mediaID, {
-                                       _dialogSetup: function() {
-                                               return {
-                                                       id: 'mediaEditor_' + media.mediaID,
-                                                       options: {
-                                                               backdropCloseOnClick: false,
-                                                               onClose: this._close.bind(this),
-                                                               title: Language.get('wcf.media.edit')
-                                                       },
-                                                       source: {
-                                                               after: (function(content, data) {
-                                                                       this._availableLanguageCount = ~~data.returnValues.availableLanguageCount;
-                                                                       this._categoryIds = data.returnValues.categoryIDs.map(function(number) {
-                                                                               return ~~number;
-                                                                       });
-                                                                       
-                                                                       var didLoadMediaData = false;
-                                                                       if (data.returnValues.mediaData) {
-                                                                               this._media = data.returnValues.mediaData;
-                                                                               
-                                                                               didLoadMediaData = true;
-                                                                       }
-                                                                       
-                                                                       // make sure that the language chooser is initialized first
-                                                                       setTimeout(function() {
-                                                                               if (this._availableLanguageCount > 1) {
-                                                                                       LanguageChooser.setLanguageId('mediaEditor_' + this._media.mediaID + '_languageID', this._media.languageID || LANGUAGE_ID);
-                                                                               }
-                                                                               
-                                                                               if (this._categoryIds.length) {
-                                                                                       elBySel('select[name=categoryID]', content).value = ~~this._media.categoryID;
-                                                                               }
-                                                                               
-                                                                               var title = elBySel('input[name=title]', content);
-                                                                               var altText = elBySel('input[name=altText]', content);
-                                                                               var caption = elBySel('textarea[name=caption]', content);
-                                                                               
-                                                                               if (this._availableLanguageCount > 1 && this._media.isMultilingual) {
-                                                                                       if (elById('altText_' + this._media.mediaID)) LanguageInput.setValues('altText_' + this._media.mediaID, Dictionary.fromObject(this._media.altText || { }));
-                                                                                       if (elById('caption_' + this._media.mediaID)) LanguageInput.setValues('caption_' + this._media.mediaID, Dictionary.fromObject(this._media.caption || { }));
-                                                                                       LanguageInput.setValues('title_' + this._media.mediaID, Dictionary.fromObject(this._media.title || { }));
-                                                                               }
-                                                                               else {
-                                                                                       title.value = this._media.title ? this._media.title[this._media.languageID || LANGUAGE_ID] : ''; 
-                                                                                       if (altText) altText.value = this._media.altText ? this._media.altText[this._media.languageID || LANGUAGE_ID] : '';
-                                                                                       if (caption) caption.value = this._media.caption ? this._media.caption[this._media.languageID || LANGUAGE_ID] : '';
-                                                                               }
-                                                                               
-                                                                               if (this._availableLanguageCount > 1) {
-                                                                                       var isMultilingual = elBySel('input[name=isMultilingual]', content);
-                                                                                       isMultilingual.addEventListener('change', this._updateLanguageFields.bind(this));
-                                                                                       
-                                                                                       this._updateLanguageFields(null, isMultilingual);
-                                                                               }
-                                                                               
-                                                                               var keyPress = this._keyPress.bind(this);
-                                                                               if (altText) altText.addEventListener('keypress', keyPress);
-                                                                               title.addEventListener('keypress', keyPress);
-                                                                               
-                                                                               elBySel('button[data-type=submit]', content).addEventListener(WCF_CLICK_EVENT, this._saveData.bind(this));
-                                                                               
-                                                                               // remove focus from input elements and scroll dialog to top
-                                                                               document.activeElement.blur();
-                                                                               elById('mediaEditor_' + this._media.mediaID).parentNode.scrollTop = 0;
-                                                                               
-                                                                               DomChangeListener.trigger();
-                                                                       }.bind(this), 200);
-                                                               }).bind(this),
-                                                               data: {
-                                                                       actionName: 'getEditorDialog',
-                                                                       className: 'wcf\\data\\media\\MediaAction',
-                                                                       objectIDs: [media.mediaID]
-                                                               }
-                                                       }
-                                               };
-                                       }.bind(this)
-                               });
-                       }
-                       
-                       UiDialog.open(this._dialogs.get('mediaEditor_' + media.mediaID));
-               }
-       };
-       
-       return MediaEditor;
-});
-
-/**
- * Uploads media files.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Media/Upload
- */
-define(
-       'WoltLabSuite/Core/Media/Upload',[
-               'Core',
-               'DateUtil',
-               'Dom/ChangeListener',
-               'Dom/Traverse',
-               'Dom/Util',
-               'EventHandler',
-               'Language',
-               'Permission',
-               'Upload',
-               'User',
-               'WoltLabSuite/Core/FileUtil'
-       ],
-       function(
-               Core,
-               DateUtil,
-               DomChangeListener,
-               DomTraverse,
-               DomUtil,
-               EventHandler,
-               Language,
-               Permission,
-               Upload,
-               User,
-               FileUtil
-       )
-{
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       _createFileElement: function() {},
-                       _getParameters: function() {},
-                       _success: function() {},
-                       _uploadFiles: function() {},
-                       _createButton: function() {},
-                       _createFileElements: function() {},
-                       _failure: function() {},
-                       _insertButton: function() {},
-                       _progress: function() {},
-                       _removeButton: function() {},
-                       _upload: function() {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * @constructor
-        */
-       function MediaUpload(buttonContainerId, targetId, options) {
-               options = options || {};
-               
-               this._mediaManager = null;
-               if (options.mediaManager) {
-                       this._mediaManager = options.mediaManager;
-                       delete options.mediaManager;
-               }
-               this._categoryId = null;
-               
-               Upload.call(this, buttonContainerId, targetId, Core.extend({
-                       className: 'wcf\\data\\media\\MediaAction',
-                       multiple: this._mediaManager ? true : false,
-                       singleFileRequests: true
-               }, options));
-       }
-       Core.inherit(MediaUpload, Upload, {
-               /**
-                * @see WoltLabSuite/Core/Upload#_createFileElement
-                */
-               _createFileElement: function(file) {
-                       var fileElement;
-                       if (this._target.nodeName === 'OL' || this._target.nodeName === 'UL') {
-                               fileElement = elCreate('li');
-                       }
-                       else if (this._target.nodeName === 'TBODY') {
-                               var firstTr = elByTag('TR', this._target)[0];
-                               var tableContainer = this._target.parentNode.parentNode;
-                               if (tableContainer.style.getPropertyValue('display') === 'none') {
-                                       fileElement = firstTr;
-                                       
-                                       tableContainer.style.removeProperty('display');
-                                       
-                                       elRemove(elById(elData(this._target, 'no-items-info')));
-                               }
-                               else {
-                                       fileElement = firstTr.cloneNode(true);
-                                       
-                                       // regenerate id of table row
-                                       fileElement.removeAttribute('id');
-                                       DomUtil.identify(fileElement);
-                               }
-                               
-                               var cells = elByTag('TD', fileElement), cell;
-                               for (var i = 0, length = cells.length; i < length; i++) {
-                                       cell = cells[i];
-                                       
-                                       if (cell.classList.contains('columnMark')) {
-                                               elBySelAll('[data-object-id]', cell, elHide);
-                                       }
-                                       else if (cell.classList.contains('columnIcon')) {
-                                               elBySelAll('[data-object-id]', cell, elHide);
-                                               
-                                               elByClass('mediaEditButton', cell)[0].classList.add('jsMediaEditButton');
-                                               elData(elByClass('jsDeleteButton', cell)[0], 'confirm-message-html', Language.get('wcf.media.delete.confirmMessage', {
-                                                       title: file.name
-                                               }));
-                                       }
-                                       else if (cell.classList.contains('columnFilename')) {
-                                               // replace copied image with spinner
-                                               var image = elByTag('IMG', cell);
-                                               
-                                               if (!image.length) {
-                                                       image = elByClass('icon48', cell);
-                                               }
-                                               
-                                               var spinner = elCreate('span');
-                                               spinner.className = 'icon icon48 fa-spinner mediaThumbnail';
-                                               
-                                               DomUtil.replaceElement(image[0], spinner);
-                                               
-                                               // replace title and uploading user
-                                               var ps = elBySelAll('.box48 > div > p', cell);
-                                               ps[0].textContent = file.name;
-                                               
-                                               var userLink = elByTag('A', ps[1])[0];
-                                               if (!userLink) {
-                                                       userLink = elCreate('a');
-                                                       elByTag('SMALL', ps[1])[0].appendChild(userLink);
-                                               }
-                                               
-                                               userLink.setAttribute('href', User.getLink());
-                                               userLink.textContent = User.username;
-                                       }
-                                       else if (cell.classList.contains('columnUploadTime')) {
-                                               cell.innerHTML = '';
-                                               cell.appendChild(DateUtil.getTimeElement(new Date()));
-                                       }
-                                       else if (cell.classList.contains('columnDigits')) {
-                                               cell.textContent = FileUtil.formatFilesize(file.size);
-                                       }
-                                       else {
-                                               // empty the other cells
-                                               cell.innerHTML = '';
-                                       }
-                               }
-                               
-                               DomUtil.prepend(fileElement, this._target);
-                               
-                               return fileElement;
-                       }
-                       else {
-                               fileElement = elCreate('p');
-                       }
-                       
-                       var thumbnail = elCreate('div');
-                       thumbnail.className = 'mediaThumbnail';
-                       fileElement.appendChild(thumbnail);
-                       
-                       var fileIcon = elCreate('span');
-                       fileIcon.className = 'icon icon144 fa-spinner';
-                       thumbnail.appendChild(fileIcon);
-                       
-                       var mediaInformation = elCreate('div');
-                       mediaInformation.className = 'mediaInformation';
-                       fileElement.appendChild(mediaInformation);
-                       
-                       var p = elCreate('p');
-                       p.className = 'mediaTitle';
-                       p.textContent = file.name;
-                       mediaInformation.appendChild(p);
-                       
-                       var progress = elCreate('progress');
-                       elAttr(progress, 'max', 100);
-                       mediaInformation.appendChild(progress);
-                       
-                       DomUtil.prepend(fileElement, this._target);
-                       
-                       DomChangeListener.trigger();
-                       
-                       return fileElement;
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Upload#_getParameters
-                */
-               _getParameters: function() {
-                       if (this._mediaManager) {
-                               var parameters = {
-                                       imagesOnly: this._mediaManager.getOption('imagesOnly')
-                               };
-                               
-                               var categoryId = this._mediaManager.getCategoryId();
-                               if (categoryId) {
-                                       parameters.categoryID = categoryId;
-                               }
-                               
-                               return Core.extend(MediaUpload._super.prototype._getParameters.call(this), parameters);
-                       }
-                       
-                       return MediaUpload._super.prototype._getParameters.call(this);
-               },
-               
-               /**
-                * Replaces the default or copied file icon with the actual file icon.
-                * 
-                * @param       {HTMLElement}   fileIcon        file icon element
-                * @param       {object}        media           media data
-                * @param       {integer}       size            size of the file icon in pixels
-                */
-               _replaceFileIcon: function(fileIcon, media, size) {
-                       if (media.tinyThumbnailType) {
-                               var img = elCreate('img');
-                               elAttr(img, 'src', media.tinyThumbnailLink);
-                               elAttr(img, 'alt', '');
-                               img.style.setProperty('width', size + 'px');
-                               img.style.setProperty('height', size + 'px');
-                               
-                               DomUtil.replaceElement(fileIcon, img);
-                       }
-                       else {
-                               fileIcon.classList.remove('fa-spinner');
-                               
-                               var fileIconName = FileUtil.getIconNameByFilename(media.filename);
-                               if (fileIconName) {
-                                       fileIconName = '-' + fileIconName;
-                               }
-                               fileIcon.classList.add('fa-file' + fileIconName + '-o');
-                       }
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Upload#_success
-                */
-               _success: function(uploadId, data) {
-                       var files = this._fileElements[uploadId];
-                       
-                       for (var i = 0, length = files.length; i < length; i++) {
-                               var file = files[i];
-                               var internalFileId = elData(file, 'internal-file-id');
-                               var media = data.returnValues.media[internalFileId];
-                               
-                               if (file.tagName === 'TR') {
-                                       if (media) {
-                                               // update object id
-                                               var objectIdElements = elBySelAll('[data-object-id]', file);
-                                               for (var i = 0, length = objectIdElements.length; i < length; i++) {
-                                                       elData(objectIdElements[i], 'object-id', ~~media.mediaID);
-                                                       elShow(objectIdElements[i]);
-                                               }
-                                               
-                                               elByClass('columnMediaID', file)[0].textContent = media.mediaID;
-                                               
-                                               // update icon
-                                               var fileIcon = elByClass('fa-spinner', file)[0];
-                                               this._replaceFileIcon(fileIcon, media, 48);
-                                       }
-                                       else {
-                                               var error = data.returnValues.errors[internalFileId];
-                                               if (!error) {
-                                                       error = {
-                                                               errorType: 'uploadFailed',
-                                                               filename: elData(file, 'filename')
-                                                       };
-                                               }
-                                               
-                                               var fileIcon = elByClass('fa-spinner', file)[0];
-                                               fileIcon.classList.remove('fa-spinner');
-                                               fileIcon.classList.add('fa-remove');
-                                               fileIcon.classList.add('pointer');
-                                               fileIcon.classList.add('jsTooltip');
-                                               elAttr(fileIcon, 'title', Language.get('wcf.global.button.delete'));
-                                               fileIcon.addEventListener(WCF_CLICK_EVENT, function (event) {
-                                                       elRemove(event.currentTarget.parentNode.parentNode.parentNode);
-                                                       
-                                                       EventHandler.fire('com.woltlab.wcf.media.upload', 'removedErroneousUploadRow');
-                                               });
-                                               
-                                               file.classList.add('uploadFailed');
-                                               
-                                               var p = elBySelAll('.columnFilename .box48 > div > p', file)[1];
-                                               
-                                               elInnerError(p, Language.get('wcf.media.upload.error.' + error.errorType, {
-                                                       filename: error.filename
-                                               }));
-                                               
-                                               elRemove(p);
-                                       }
-                               }
-                               else {
-                                       elRemove(DomTraverse.childByTag(DomTraverse.childByClass(file, 'mediaInformation'), 'PROGRESS'));
-                                       
-                                       if (media) {
-                                               var fileIcon = DomTraverse.childByTag(DomTraverse.childByClass(file, 'mediaThumbnail'), 'SPAN');
-                                               this._replaceFileIcon(fileIcon, media, 144);
-                                               
-                                               file.className = 'jsClipboardObject mediaFile';
-                                               elData(file, 'object-id', media.mediaID);
-                                               
-                                               if (this._mediaManager) {
-                                                       this._mediaManager.setupMediaElement(media, file);
-                                                       this._mediaManager.addMedia(media, file);
-                                               }
-                                       }
-                                       else {
-                                               var error = data.returnValues.errors[internalFileId];
-                                               if (!error) {
-                                                       error = {
-                                                               errorType: 'uploadFailed',
-                                                               filename: elData(file, 'filename')
-                                                       };
-                                               }
-                                               
-                                               var fileIcon = DomTraverse.childByTag(DomTraverse.childByClass(file, 'mediaThumbnail'), 'SPAN');
-                                               fileIcon.classList.remove('fa-spinner');
-                                               fileIcon.classList.add('fa-remove');
-                                               fileIcon.classList.add('pointer');
-                                               
-                                               file.classList.add('uploadFailed');
-                                               file.classList.add('jsTooltip');
-                                               elAttr(file, 'title', Language.get('wcf.global.button.delete'));
-                                               file.addEventListener(WCF_CLICK_EVENT, function () {
-                                                       elRemove(this);
-                                               });
-                                               
-                                               var title = DomTraverse.childByClass(DomTraverse.childByClass(file, 'mediaInformation'), 'mediaTitle');
-                                               title.innerText = Language.get('wcf.media.upload.error.' + error.errorType, {
-                                                       filename: error.filename
-                                               });
-                                       }
-                               }
-                               
-                               DomChangeListener.trigger();
-                       }
-                       
-                       EventHandler.fire('com.woltlab.wcf.media.upload', 'success', {
-                               files: files,
-                               isMultiFileUpload: this._multiFileUploadIds.indexOf(uploadId) !== -1,
-                               media: data.returnValues.media,
-                               upload: this,
-                               uploadId: uploadId
-                       });
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Upload#_uploadFiles
-                */
-               _uploadFiles: function(files, blob) {
-                       return MediaUpload._super.prototype._uploadFiles.call(this, files, blob);
-               }
-       });
-       
-       return MediaUpload;
-});
-
-/**
- * Uploads media files.
- *
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Media/List/Upload
- */
-define(
-       'WoltLabSuite/Core/Media/List/Upload',[
-               'Core', 'Dom/Util', '../Upload'
-       ],
-       function(
-               Core, DomUtil, MediaUpload
-       )
-{
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       _createButton: function() {},
-                       _success: function() {},
-                       _upload: function() {},
-                       _createFileElement: function() {},
-                       _getParameters: function() {},
-                       _uploadFiles: function() {},
-                       _createFileElements: function() {},
-                       _failure: function() {},
-                       _insertButton: function() {},
-                       _progress: function() {},
-                       _removeButton: function() {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * @constructor
-        */
-       function MediaListUpload(buttonContainerId, targetId, options) {
-               MediaUpload.call(this, buttonContainerId, targetId, options);
-       }
-       Core.inherit(MediaListUpload, MediaUpload, {
-               /**
-                * Creates the upload button.
-                */
-               _createButton: function() {
-                       MediaListUpload._super.prototype._createButton.call(this);
-                       
-                       var span = elBySel('span', this._button);
-                       
-                       var space = document.createTextNode(' ');
-                       DomUtil.prepend(space, span);
-                       
-                       var icon = elCreate('span');
-                       icon.className = 'icon icon16 fa-upload';
-                       DomUtil.prepend(icon, span);
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Upload#_getParameters
-                */
-               _getParameters: function() {
-                       if (this._options.categoryId) {
-                               return Core.extend(MediaListUpload._super.prototype._getParameters.call(this), {
-                                       categoryID: this._options.categoryId
-                               });
-                       }
-                       
-                       return MediaListUpload._super.prototype._getParameters.call(this);
-               }
-       });
-       
-       return MediaListUpload;
-});
-
-/**
- * Initializes modules required for media clipboard.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Media/Clipboard
- */
-define('WoltLabSuite/Core/Media/Clipboard',[
-               'Ajax',
-               'Dom/ChangeListener',
-               'EventHandler',
-               'Language',
-               'Ui/Dialog',
-               'Ui/Notification',
-               'WoltLabSuite/Core/Controller/Clipboard',
-               'WoltLabSuite/Core/Media/Editor',
-               'WoltLabSuite/Core/Media/List/Upload'
-       ],
-       function(
-               Ajax,
-               DomChangeListener,
-               EventHandler,
-               Language,
-               UiDialog,
-               UiNotification,
-               Clipboard,
-               MediaEditor,
-               MediaListUpload
-       ) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _ajaxSetup: function() {},
-                       _ajaxSuccess: function() {},
-                       _clipboardAction: function() {},
-                       _dialogSetup: function() {},
-                       _edit: function() {},
-                       _setCategory: function() {}
-               };
-               return Fake;
-       }
-       
-       var _clipboardObjectIds = [];
-       var _mediaManager;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Media/Clipboard
-        */
-       return {
-               init: function(pageClassName, hasMarkedItems, mediaManager) {
-                       Clipboard.setup({
-                               hasMarkedItems: hasMarkedItems,
-                               pageClassName: pageClassName
-                       });
-                       
-                       _mediaManager = mediaManager;
-                       
-                       EventHandler.add('com.woltlab.wcf.clipboard', 'com.woltlab.wcf.media', this._clipboardAction.bind(this));
-               },
-               
-               /**
-                * Returns the data used to setup the AJAX request object.
-                *
-                * @return      {object}        setup data
-                */
-               _ajaxSetup: function() {
-                       return {
-                               data: {
-                                       className: 'wcf\\data\\media\\MediaAction'
-                               }
-                       }
-               },
-               
-               /**
-                * Handles successful AJAX request.
-                *
-                * @param       {object}        data    response data
-                */
-               _ajaxSuccess: function(data) {
-                       switch (data.actionName) {
-                               case 'getSetCategoryDialog':
-                                       UiDialog.open(this, data.returnValues.template);
-                                       
-                                       break;
-                                       
-                               case 'setCategory':
-                                       UiDialog.close(this);
-                                       
-                                       UiNotification.show();
-                                       
-                                       Clipboard.reload();
-                                       
-                                       break;
-                       }
-               },
-               
-               /**
-                * Returns the data used to setup the dialog.
-                * 
-                * @return      {object}        setup data
-                */
-               _dialogSetup: function() {
-                       return {
-                               id: 'mediaSetCategoryDialog',
-                               options: {
-                                       onSetup: function(content) {
-                                               elBySel('button', content).addEventListener(WCF_CLICK_EVENT, function(event) {
-                                                       event.preventDefault();
-                                                       
-                                                       this._setCategory(~~elBySel('select[name="categoryID"]', content).value);
-                                                       
-                                                       event.currentTarget.disabled = true;
-                                               }.bind(this));
-                                       }.bind(this),
-                                       title: Language.get('wcf.media.setCategory')
-                               },
-                               source: null
-                       }
-               },
-               
-               /**
-                * Handles successful clipboard actions.
-                * 
-                * @param       {object}        actionData
-                */
-               _clipboardAction: function(actionData) {
-                       var mediaIds = actionData.data.parameters.objectIDs;
-                       
-                       switch (actionData.data.actionName) {
-                               case 'com.woltlab.wcf.media.delete':
-                                       // only consider events if the action has been executed
-                                       if (actionData.responseData !== null) {
-                                               _mediaManager.clipboardDeleteMedia(mediaIds);
-                                       }
-                                       
-                                       break;
-                                       
-                               case 'com.woltlab.wcf.media.insert':
-                                       _mediaManager.clipboardInsertMedia(mediaIds);
-                                       
-                                       break;
-                                       
-                               case 'com.woltlab.wcf.media.setCategory':
-                                       _clipboardObjectIds = mediaIds;
-                                       
-                                       Ajax.api(this, {
-                                               actionName: 'getSetCategoryDialog'
-                                       });
-                                       
-                                       break;
-                       }
-               },
-               
-               /**
-                * Sets the category of the marked media files.
-                * 
-                * @param       {int}           categoryID      selected category id
-                */
-               _setCategory: function(categoryID) {
-                       Ajax.api(this, {
-                               actionName: 'setCategory',
-                               objectIDs: _clipboardObjectIds,
-                               parameters: {
-                                       categoryID: categoryID
-                               }
-                       });
-               }
-       }
-});
-/**
- * Provides desktop notifications via periodic polling with an
- * increasing request delay on inactivity.
- * 
- * @author      Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module      WoltLabSuite/Core/Notification/Handler
- */
-define('WoltLabSuite/Core/Notification/Handler',['Ajax', 'Core', 'EventHandler', 'StringUtil'], function(Ajax, Core, EventHandler, StringUtil) {
-       "use strict";
-       
-       if (!('Promise' in window) || !('Notification' in window)) {
-               // fake object exposed to ancient browsers (*cough* IE11 *cough*)
-               return {
-                       setup: function () {}
-               }
-       }
-       
-       var _allowNotification = false;
-       var _icon = '';
-       var _inactiveSince = 0;
-       //noinspection JSUnresolvedVariable
-       var _lastRequestTimestamp = window.TIME_NOW;
-       var _requestTimer = null;
-       var _sessionKeepAlive = 0;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Notification/Handler
-        */
-       return {
-               /**
-                * Initializes the desktop notification system.
-                * 
-                * @param       {Object}        options         initialization options
-                */
-               setup: function (options) {
-                       options = Core.extend({
-                               enableNotifications: false,
-                               icon: '',
-                               sessionKeepAlive: 0
-                       }, options);
-                       
-                       _icon = options.icon;
-                       _sessionKeepAlive = options.sessionKeepAlive * 60;
-                       
-                       this._prepareNextRequest();
-                       
-                       document.addEventListener('visibilitychange', this._onVisibilityChange.bind(this));
-                       window.addEventListener('storage', this._onStorage.bind(this));
-                       
-                       this._onVisibilityChange(null);
-                       
-                       if (options.enableNotifications) {
-                               switch (window.Notification.permission) {
-                                       case 'granted':
-                                               _allowNotification = true;
-                                               break;
-                                       case 'default':
-                                               window.Notification.requestPermission(function (result) {
-                                                       if (result === 'granted') {
-                                                               _allowNotification = true;
-                                                       }
-                                               });
-                                               break;
-                               }
-                       }
-               },
-               
-               /**
-                * Detects when this window is hidden or restored.
-                * 
-                * @param       {Event}         event
-                * @protected
-                */
-               _onVisibilityChange: function(event) {
-                       // document was hidden before
-                       if (event !== null && !document.hidden) {
-                               var difference = (Date.now() - _inactiveSince) / 60000;
-                               if (difference > 4) {
-                                       this._resetTimer();
-                                       this._dispatchRequest();
-                               }
-                       }
-                       
-                       _inactiveSince = (document.hidden) ? Date.now() : 0;
-               },
-               
-               /**
-                * Returns the delay in minutes before the next request should be dispatched.
-                * 
-                * @return      {int}
-                * @protected
-                */
-               _getNextDelay: function() {
-                       if (_inactiveSince === 0) return 5;
-                       
-                       // milliseconds -> minutes
-                       var inactiveMinutes = ~~((Date.now() - _inactiveSince) / 60000);
-                       if (inactiveMinutes < 15) {
-                               return 5;
-                       }
-                       else if (inactiveMinutes < 30) {
-                               return 10;
-                       }
-                       
-                       return 15;
-               },
-               
-               /**
-                * Resets the request delay timer.
-                * 
-                * @protected
-                */
-               _resetTimer: function() {
-                       if (_requestTimer !== null) {
-                               window.clearTimeout(_requestTimer);
-                               _requestTimer = null;
-                       }
-               },
-               
-               /**
-                * Schedules the next request using a calculated delay.
-                * 
-                * @protected
-                */
-               _prepareNextRequest: function() {
-                       this._resetTimer();
-                       
-                       var delay = Math.min(this._getNextDelay(), _sessionKeepAlive);
-                       _requestTimer = window.setTimeout(this._dispatchRequest.bind(this), delay * 60000);
-               },
-               
-               /**
-                * Requests new data from the server.
-                * 
-                * @protected
-                */
-               _dispatchRequest: function() {
-                       var parameters = {};
-                       EventHandler.fire('com.woltlab.wcf.notification', 'beforePoll', parameters);
-                       
-                       // this timestamp is used to determine new notifications and to avoid
-                       // notifications being displayed multiple times due to different origins
-                       // (=subdomains) used, because we cannot synchronize them in the client
-                       parameters.lastRequestTimestamp = _lastRequestTimestamp;
-                       
-                       Ajax.api(this, {
-                               parameters: parameters
-                       });
-               },
-               
-               /**
-                * Notifies subscribers for updated data received by another tab.
-                * 
-                * @protected
-                */
-               _onStorage: function() {
-                       // abort and re-schedule periodic request
-                       this._prepareNextRequest();
-                       
-                       var pollData, keepAliveData, abort = false;
-                       try {
-                               pollData = window.localStorage.getItem(Core.getStoragePrefix() + 'notification');
-                               keepAliveData = window.localStorage.getItem(Core.getStoragePrefix() + 'keepAliveData');
-                               
-                               pollData = JSON.parse(pollData);
-                               keepAliveData = JSON.parse(keepAliveData);
-                       }
-                       catch (e) {
-                               abort = true;
-                       }
-                       
-                       if (!abort) {
-                               EventHandler.fire('com.woltlab.wcf.notification', 'onStorage', {
-                                       pollData: pollData,
-                                       keepAliveData: keepAliveData
-                               });
-                       }
-               },
-               
-               _ajaxSuccess: function(data) {
-                       var abort = false;
-                       var keepAliveData = data.returnValues.keepAliveData;
-                       var pollData = data.returnValues.pollData;
-                       
-                       // forward keep alive data
-                       window.WCF.System.PushNotification.executeCallbacks(keepAliveData);
-                       
-                       // store response data in local storage
-                       try {
-                               window.localStorage.setItem(Core.getStoragePrefix() + 'notification', JSON.stringify(pollData));
-                               window.localStorage.setItem(Core.getStoragePrefix() + 'keepAliveData', JSON.stringify(keepAliveData));
-                       }
-                       catch (e) {
-                               // storage is unavailable, e.g. in private mode, log error and disable polling
-                               abort = true;
-                               
-                               window.console.log(e);
-                       }
-                       
-                       if (!abort) {
-                               this._prepareNextRequest();
-                       }
-                       
-                       _lastRequestTimestamp = data.returnValues.lastRequestTimestamp;
-                       
-                       EventHandler.fire('com.woltlab.wcf.notification', 'afterPoll', pollData);
-                       
-                       this._showNotification(pollData);
-               },
-               
-               /**
-                * Displays a desktop notification.
-                * 
-                * @param       {Object}        pollData
-                * @protected
-                */
-               _showNotification: function(pollData) {
-                       if (!_allowNotification) {
-                               return;
-                       }
-                       
-                       //noinspection JSUnresolvedVariable
-                       if (typeof pollData.notification === 'object' && typeof pollData.notification.message ===  'string') {
-                               //noinspection JSUnresolvedVariable
-                               var notification = new window.Notification(pollData.notification.title, {
-                                       body: StringUtil.unescapeHTML(pollData.notification.message),
-                                       icon: _icon
-                               });
-                               notification.onclick = function () {
-                                       window.focus();
-                                       notification.close();
-                                       
-                                       //noinspection JSUnresolvedVariable
-                                       window.location = pollData.notification.link;
-                               };
-                       }
-               },
-               
-               _ajaxSetup: function() {
-                       //noinspection JSUnresolvedVariable
-                       return {
-                               data: {
-                                       actionName: 'poll',
-                                       className: 'wcf\\data\\session\\SessionAction'
-                               },
-                               ignoreError: !window.ENABLE_DEBUG_MODE,
-                               silent: !window.ENABLE_DEBUG_MODE
-                       };
-               }
-       }
-});
-
-/**
- * Drag and Drop file uploads.
- * 
- * @author      Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module      WoltLabSuite/Core/Ui/Redactor/DragAndDrop
- */
-define('WoltLabSuite/Core/Ui/Redactor/DragAndDrop',['Dictionary', 'EventHandler', 'Language'], function (Dictionary, EventHandler, Language) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _dragOver: function() {},
-                       _drop: function() {},
-                       _dragLeave: function() {},
-                       _setup: function() {}
-               };
-               return Fake;
-       }
-       
-       var _didInit = false;
-       var _dragArea = new Dictionary();
-       var _isDragging = false;
-       var _isFile = false;
-       var _timerLeave = null;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Redactor/DragAndDrop
-        */
-       return {
-               /**
-                * Initializes drag and drop support for provided editor instance.
-                * 
-                * @param       {$.Redactor}    editor          editor instance
-                */
-               init: function (editor) {
-                       if (!_didInit) {
-                               this._setup();
-                       }
-                       
-                       _dragArea.set(editor.uuid, {
-                               editor: editor,
-                               element: null
-                       });
-               },
-               
-               /**
-                * Handles items dragged into the browser window.
-                * 
-                * @param       {Event}         event           drag event
-                */
-               _dragOver: function (event) {
-                       event.preventDefault();
-                       
-                       //noinspection JSUnresolvedVariable
-                       if (!event.dataTransfer || !event.dataTransfer.types) {
-                               return;
-                       }
-                       
-                       var isFirefox = false;
-                       //noinspection JSUnresolvedVariable
-                       for (var property in event.dataTransfer) {
-                               //noinspection JSUnresolvedVariable
-                               if (event.dataTransfer.hasOwnProperty(property) && property.match(/^moz/)) {
-                                       isFirefox = true;
-                                       break;
-                               }
-                       }
-                       
-                       // IE and WebKit set 'Files', Firefox sets 'application/x-moz-file' for files being dragged
-                       // and Safari just provides 'Files' along with a huge list of garbage
-                       _isFile = false;
-                       if (isFirefox) {
-                               // Firefox sets the 'Files' type even if the user is just dragging an on-page element
-                               //noinspection JSUnresolvedVariable
-                               if (event.dataTransfer.types[0] === 'application/x-moz-file') {
-                                       _isFile = true;
-                               }
-                       }
-                       else {
-                               //noinspection JSUnresolvedVariable
-                               for (var i = 0; i < event.dataTransfer.types.length; i++) {
-                                       //noinspection JSUnresolvedVariable
-                                       if (event.dataTransfer.types[i] === 'Files') {
-                                               _isFile = true;
-                                               break;
-                                       }
-                               }
-                       }
-                       
-                       if (!_isFile) {
-                               // user is just dragging around some garbage, ignore it
-                               return;
-                       }
-                       
-                       if (_isDragging) {
-                               // user is still dragging the file around
-                               return;
-                       }
-                       
-                       _isDragging = true;
-                       
-                       _dragArea.forEach((function (data, uuid) {
-                               var editor = data.editor.$editor[0];
-                               if (!editor.parentNode) {
-                                       _dragArea.delete(uuid);
-                                       return;
-                               }
-                               
-                               var element = data.element;
-                               if (element === null) {
-                                       element = elCreate('div');
-                                       element.className = 'redactorDropArea';
-                                       elData(element, 'element-id', data.editor.$element[0].id);
-                                       elData(element, 'drop-here', Language.get('wcf.attachment.dragAndDrop.dropHere'));
-                                       elData(element, 'drop-now', Language.get('wcf.attachment.dragAndDrop.dropNow'));
-                                       
-                                       element.addEventListener('dragover', function () { element.classList.add('active'); });
-                                       element.addEventListener('dragleave', function () { element.classList.remove('active'); });
-                                       element.addEventListener('drop', this._drop.bind(this));
-                                       
-                                       data.element = element;
-                               }
-                               
-                               editor.parentNode.insertBefore(element, editor);
-                               element.style.setProperty('top', editor.offsetTop + 'px', '');
-                       }).bind(this));
-               },
-               
-               /**
-                * Handles items dropped onto an editor's drop area
-                * 
-                * @param       {Event}         event           drop event
-                * @protected
-                */
-               _drop: function (event) {
-                       if (!_isFile) {
-                               return;
-                       }
-                       
-                       //noinspection JSUnresolvedVariable
-                       if (!event.dataTransfer || !event.dataTransfer.files.length) {
-                               return;
-                       }
-                       
-                       event.preventDefault();
-                       
-                       //noinspection JSCheckFunctionSignatures
-                       var elementId = elData(event.currentTarget, 'element-id');
-                       
-                       //noinspection JSUnresolvedVariable
-                       for (var i = 0, length = event.dataTransfer.files.length; i < length; i++) {
-                               //noinspection JSUnresolvedVariable
-                               EventHandler.fire('com.woltlab.wcf.redactor2', 'dragAndDrop_' + elementId, {
-                                       file: event.dataTransfer.files[i]
-                               });
-                       }
-                       
-                       // this will reset all drop areas
-                       this._dragLeave();
-               },
-               
-               /**
-                * Invoked whenever the item is no longer dragged or was dropped.
-                * 
-                * @protected
-                */
-               _dragLeave: function () {
-                       if (!_isDragging || !_isFile) {
-                               return;
-                       }
-                       
-                       if (_timerLeave !== null) {
-                               window.clearTimeout(_timerLeave);
-                       }
-                       
-                       _timerLeave = window.setTimeout(function () {
-                               if (!_isDragging) {
-                                       _dragArea.forEach(function (data) {
-                                               if (data.element && data.element.parentNode) {
-                                                       data.element.classList.remove('active');
-                                                       elRemove(data.element);
-                                               }
-                                       });
-                               }
-                               
-                               _timerLeave = null;
-                       }, 100);
-                       
-                       _isDragging = false;
-               },
-               
-               /**
-                * Handles the global drop event.
-                * 
-                * @param       {Event}         event
-                * @protected
-                */
-               _globalDrop: function (event) {
-                       if (event.target.closest('.redactor-layer') === null) {
-                               var eventData = { cancelDrop: true, event: event };
-                               _dragArea.forEach(function(data) {
-                                       //noinspection JSUnresolvedVariable
-                                       EventHandler.fire('com.woltlab.wcf.redactor2', 'dragAndDrop_globalDrop_' + data.editor.$element[0].id, eventData);
-                               });
-                               
-                               if (eventData.cancelDrop) {
-                                       event.preventDefault();
-                               }
-                       }
-                       
-                       this._dragLeave(event);
-               },
-               
-               /**
-                * Binds listeners to global events.
-                * 
-                * @protected
-                */
-               _setup: function () {
-                       // discard garbage event
-                       window.addEventListener('dragend', function (event) { event.preventDefault(); });
-                       
-                       window.addEventListener('dragover', this._dragOver.bind(this));
-                       window.addEventListener('dragleave', this._dragLeave.bind(this));
-                       window.addEventListener('drop', this._globalDrop.bind(this));
-                       
-                       _didInit = true;
-               }
-       };
-});
-
-/**
- * Generic interface for drag and Drop file uploads.
- * 
- * @author      Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module      WoltLabSuite/Core/Ui/DragAndDrop
- */
-define('WoltLabSuite/Core/Ui/DragAndDrop',['Core', 'EventHandler', 'WoltLabSuite/Core/Ui/Redactor/DragAndDrop'], function (Core, EventHandler, UiRedactorDragAndDrop) {
-       /**
-        * @exports     WoltLabSuite/Core/Ui/DragAndDrop
-        */
-       return {
-               /**
-                * @param       {Object}        options
-                */
-               register: function (options) {
-                       var uuid = Core.getUuid();
-                       options = Core.extend({
-                               element: '',
-                               elementId: '',
-                               onDrop: function(data) {
-                                       /* data: { file: File } */
-                               },
-                               onGlobalDrop: function (data) {
-                                       /* data: { cancelDrop: boolean, event: DragEvent } */
-                               }
-                       });
-                       
-                       EventHandler.add('com.woltlab.wcf.redactor2', 'dragAndDrop_' + options.elementId, options.onDrop);
-                       EventHandler.add('com.woltlab.wcf.redactor2', 'dragAndDrop_globalDrop_' + options.elementId, options.onGlobalDrop);
-                       
-                       UiRedactorDragAndDrop.init({
-                               uuid: uuid,
-                               $editor: [options.element],
-                               $element: [{id: options.elementId}]
-                       });
-               }
-       };
-});
-
-/**
- * Flexible UI element featuring both a list of items and an input field with suggestion support.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Suggestion
- */
-define('WoltLabSuite/Core/Ui/Suggestion',['Ajax', 'Core', 'Ui/SimpleDropdown'], function(Ajax, Core, UiSimpleDropdown) {
-       "use strict";
-       
-       /**
-        * @constructor
-        * @param       {string}                elementId       input element id
-        * @param       {Object}                options         option list
-        */
-       function UiSuggestion(elementId, options) { this.init(elementId, options); }
-       UiSuggestion.prototype = {
-               /**
-                * Initializes a new suggestion input.
-                * 
-                * @param       {string}                elementId       input element id
-                * @param       {Object}                options         option list
-                */
-               init: function(elementId, options) {
-                       this._dropdownMenu = null;
-                       this._value = '';
-                       
-                       this._element = elById(elementId);
-                       if (this._element === null) {
-                               throw new Error("Expected a valid element id.");
-                       }
-                       
-                       this._options = Core.extend({
-                               ajax: {
-                                       actionName: 'getSearchResultList',
-                                       className: '',
-                                       interfaceName: 'wcf\\data\\ISearchAction',
-                                       parameters: {
-                                               data: {}
-                                       }
-                               },
-                               
-                               // will be executed once a value from the dropdown has been selected
-                               callbackSelect: null,
-                               // list of excluded search values
-                               excludedSearchValues: [],
-                               // minimum number of characters required to trigger a search request
-                               threshold: 3
-                       }, options);
-                       
-                       if (typeof this._options.callbackSelect !== 'function') {
-                               throw new Error("Expected a valid callback for option 'callbackSelect'.");
-                       }
-                       
-                       this._element.addEventListener(WCF_CLICK_EVENT, function(event) { event.stopPropagation(); });
-                       this._element.addEventListener('keydown', this._keyDown.bind(this));
-                       this._element.addEventListener('keyup', this._keyUp.bind(this));
-               },
-               
-               /**
-                * Adds an excluded search value.
-                * 
-                * @param       {string}        value           excluded value
-                */
-               addExcludedValue: function(value) {
-                       if (this._options.excludedSearchValues.indexOf(value) === -1) {
-                               this._options.excludedSearchValues.push(value);
-                       }
-               },
-               
-               /**
-                * Removes an excluded search value.
-                * 
-                * @param       {string}        value           excluded value
-                */
-               removeExcludedValue: function(value) {
-                       var index = this._options.excludedSearchValues.indexOf(value);
-                       if (index !== -1) {
-                               this._options.excludedSearchValues.splice(index, 1);
-                       }
-               },
-               
-               /**
-                * Returns true if the suggestions are active.
-                * @return      {boolean}
-                */
-               isActive: function() {
-                       return (this._dropdownMenu !== null && UiSimpleDropdown.isOpen(this._element.id));
-               },
-               
-               /**
-                * Handles the keyboard navigation for interaction with the suggestion list.
-                * 
-                * @param       {object}        event           event object
-                */
-               _keyDown: function(event) {
-                       if (!this.isActive()) {
-                               return true;
-                       }
-                       
-                       if (event.keyCode !== 13 && event.keyCode !== 27 && event.keyCode !== 38 && event.keyCode !== 40) {
-                               return true;
-                       }
-                       
-                       var active, i = 0, length = this._dropdownMenu.childElementCount;
-                       while (i < length) {
-                               active = this._dropdownMenu.children[i];
-                               if (active.classList.contains('active')) {
-                                       break;
-                               }
-                               
-                               i++;
-                       }
-                       
-                       if (event.keyCode === 13) {
-                               // Enter
-                               UiSimpleDropdown.close(this._element.id);
-                               
-                               this._select(active);
-                       }
-                       else if (event.keyCode === 27) {
-                               if (UiSimpleDropdown.isOpen(this._element.id)) {
-                                       UiSimpleDropdown.close(this._element.id);
-                               }
-                               else {
-                                       // let the event pass through
-                                       return true;
-                               }
-                       }
-                       else {
-                               var index = 0;
-                               
-                               if (event.keyCode === 38) {
-                                       // ArrowUp
-                                       index = ((i === 0) ? length : i) - 1;
-                               }
-                               else if (event.keyCode === 40) {
-                                       // ArrowDown
-                                       index = i + 1;
-                                       if (index === length) index = 0;
-                               }
-                               
-                               if (index !== i) {
-                                       active.classList.remove('active');
-                                       this._dropdownMenu.children[index].classList.add('active');
-                               }
-                       }
-                       
-                       event.preventDefault();
-                       return false;
-               },
-               
-               /**
-                * Selects an item from the list.
-                * 
-                * @param       {(Element|Event)}       item    list item or event object
-                */
-               _select: function(item) {
-                       var isEvent = (item instanceof Event);
-                       if (isEvent) {
-                               item = item.currentTarget.parentNode;
-                       }
-                       
-                       var anchor = item.children[0];
-                       this._options.callbackSelect(this._element.id, { objectId: elData(anchor, 'object-id'), value: item.textContent, type: elData(anchor, 'type') });
-                       
-                       if (isEvent) {
-                               this._element.focus();
-                       }
-               },
-               
-               /**
-                * Performs a search for the input value unless it is below the threshold.
-                * 
-                * @param       {object}                event           event object
-                */
-               _keyUp: function(event) {
-                       var value = event.currentTarget.value.trim();
-                       
-                       if (this._value === value) {
-                               return;
-                       }
-                       else if (value.length < this._options.threshold) {
-                               if (this._dropdownMenu !== null) {
-                                       UiSimpleDropdown.close(this._element.id);
-                               }
-                               
-                               this._value = value;
-                               
-                               return;
-                       }
-                       
-                       this._value = value;
-                       
-                       Ajax.api(this, {
-                               parameters: {
-                                       data: {
-                                               excludedSearchValues: this._options.excludedSearchValues,
-                                               searchString: value
-                                       }
-                               }
-                       });
-               },
-               
-               _ajaxSetup: function() {
-                       return {
-                               data: this._options.ajax
-                       };
-               },
-               
-               /**
-                * Handles successful Ajax requests.
-                * 
-                * @param       {object}        data            response values
-                */
-               _ajaxSuccess: function(data) {
-                       if (this._dropdownMenu === null) {
-                               this._dropdownMenu = elCreate('div');
-                               this._dropdownMenu.className = 'dropdownMenu';
-                               
-                               UiSimpleDropdown.initFragment(this._element, this._dropdownMenu);
-                       }
-                       else {
-                               this._dropdownMenu.innerHTML = '';
-                       }
-                       
-                       if (data.returnValues.length) {
-                               var anchor, item, listItem;
-                               for (var i = 0, length = data.returnValues.length; i < length; i++) {
-                                       item = data.returnValues[i];
-                                       
-                                       anchor = elCreate('a');
-                                       if (item.icon) {
-                                               anchor.className = 'box16';
-                                               anchor.innerHTML = item.icon + ' <span></span>';
-                                               anchor.children[1].textContent = item.label;
-                                       }
-                                       else {
-                                               anchor.textContent = item.label;
-                                       }
-                                       elData(anchor, 'object-id', item.objectID);
-                                       if (item.type) elData(anchor, 'type', item.type);
-                                       anchor.addEventListener(WCF_CLICK_EVENT, this._select.bind(this));
-                                       
-                                       listItem = elCreate('li');
-                                       if (i === 0) listItem.className = 'active';
-                                       listItem.appendChild(anchor);
-                                       
-                                       this._dropdownMenu.appendChild(listItem);
-                               }
-                               
-                               UiSimpleDropdown.open(this._element.id, true);
-                       }
-                       else {
-                               UiSimpleDropdown.close(this._element.id);
-                       }
-               }
-       };
-       
-       return UiSuggestion;
-});
-
-/**
- * Flexible UI element featuring both a list of items and an input field with suggestion support.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/ItemList
- */
-define('WoltLabSuite/Core/Ui/ItemList',['Core', 'Dictionary', 'Language', 'Dom/Traverse', 'EventKey', 'WoltLabSuite/Core/Ui/Suggestion', 'Ui/SimpleDropdown'], function(Core, Dictionary, Language, DomTraverse, EventKey, UiSuggestion, UiSimpleDropdown) {
-       "use strict";
-       
-       var _activeId = '';
-       var _data = new Dictionary();
-       var _didInit = false;
-       
-       var _callbackKeyDown = null;
-       var _callbackKeyPress = null;
-       var _callbackKeyUp = null;
-       var _callbackPaste = null;
-       var _callbackRemoveItem = null;
-       var _callbackBlur = null;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/ItemList
-        */
-       return {
-               /**
-                * Initializes an item list.
-                * 
-                * The `values` argument must be empty or contain a list of strings or object, e.g.
-                * `['foo', 'bar']` or `[{ objectId: 1337, value: 'baz'}, {...}]`
-                * 
-                * @param       {string}        elementId       input element id
-                * @param       {Array}         values          list of existing values
-                * @param       {Object}        options         option list
-                */
-               init: function(elementId, values, options) {
-                       var element = elById(elementId);
-                       if (element === null) {
-                               throw new Error("Expected a valid element id, '" + elementId + "' is invalid.");
-                       }
-                       
-                       // remove data from previous instance
-                       if (_data.has(elementId)) {
-                               var tmp = _data.get(elementId);
-                               
-                               for (var key in tmp) {
-                                       if (tmp.hasOwnProperty(key)) {
-                                               var el = tmp[key];
-                                               if (el instanceof Element && el.parentNode) {
-                                                       elRemove(el);
-                                               }
-                                       }
-                               }
-                               
-                               UiSimpleDropdown.destroy(elementId);
-                               _data.delete(elementId);
-                       }
-                       
-                       options = Core.extend({
-                               // search parameters for suggestions
-                               ajax: {
-                                       actionName: 'getSearchResultList',
-                                       className: '',
-                                       data: {}
-                               },
-                               
-                               // list of excluded string values, e.g. `['ignore', 'these strings', 'when', 'searching']`
-                               excludedSearchValues: [],
-                               // maximum number of items this list may contain, `-1` for infinite
-                               maxItems: -1,
-                               // maximum length of an item value, `-1` for infinite
-                               maxLength: -1,
-                               // disallow custom values, only values offered by the suggestion dropdown are accepted
-                               restricted: false,
-                               
-                               // initial value will be interpreted as comma separated value and submitted as such
-                               isCSV: false,
-                               
-                               // will be invoked whenever the items change, receives the element id first and list of values second
-                               callbackChange: null,
-                               // callback once the form is about to be submitted
-                               callbackSubmit: null,
-                               // Callback for the custom shadow synchronization.
-                               callbackSyncShadow: null,
-                               // Callback to set values during the setup.
-                               callbackSetupValues: null,
-                               // value may contain the placeholder `{$objectId}`
-                               submitFieldName: ''
-                       }, options);
-                       
-                       var form = DomTraverse.parentByTag(element, 'FORM');
-                       if (form !== null) {
-                               if (options.isCSV === false) {
-                                       if (!options.submitFieldName.length && typeof options.callbackSubmit !== 'function') {
-                                               throw new Error("Expected a valid function for option 'callbackSubmit', a non-empty value for option 'submitFieldName' or enabling the option 'submitFieldCSV'.");
-                                       }
-                                       
-                                       form.addEventListener('submit', (function() {
-                                               var values = this.getValues(elementId);
-                                               if (options.submitFieldName.length) {
-                                                       var input;
-                                                       for (var i = 0, length = values.length; i < length; i++) {
-                                                               input = elCreate('input');
-                                                               input.type = 'hidden';
-                                                               input.name = options.submitFieldName.replace(/{$objectId}/, values[i].objectId);
-                                                               input.value = values[i].value;
-                                                               
-                                                               form.appendChild(input);
-                                                       }
-                                               }
-                                               else {
-                                                       options.callbackSubmit(form, values);
-                                               }
-                                       }).bind(this));
-                               }
-                       }
-                       
-                       this._setup();
-                       
-                       var data = this._createUI(element, options);
-                       //noinspection JSUnresolvedVariable
-                       var suggestion = new UiSuggestion(elementId, {
-                               ajax: options.ajax,
-                               callbackSelect: this._addItem.bind(this),
-                               excludedSearchValues: options.excludedSearchValues
-                       });
-                       
-                       _data.set(elementId, {
-                               dropdownMenu: null,
-                               element: data.element,
-                               list: data.list,
-                               listItem: data.element.parentNode,
-                               options: options,
-                               shadow: data.shadow,
-                               suggestion: suggestion
-                       });
-                       
-                       if (options.callbackSetupValues) {
-                               values = options.callbackSetupValues();
-                       }
-                       else {
-                               values = (data.values.length) ? data.values : values;
-                       }
-                       
-                       if (Array.isArray(values)) {
-                               var value;
-                               for (var i = 0, length = values.length; i < length; i++) {
-                                       value = values[i];
-                                       if (typeof value === 'string') {
-                                               value = { objectId: 0, value: value };
-                                       }
-                                       
-                                       this._addItem(elementId, value);
-                               }
-                       }
-               },
-               
-               /**
-                * Returns the list of current values.
-                * 
-                * @param       {string}        elementId       input element id
-                * @return      {Array}         list of objects containing object id and value
-                */
-               getValues: function(elementId) {
-                       if (!_data.has(elementId)) {
-                               throw new Error("Element id '" + elementId + "' is unknown.");
-                       }
-                       
-                       var data = _data.get(elementId);
-                       var values = [];
-                       elBySelAll('.item > span', data.list, function(span) {
-                               values.push({
-                                       objectId: ~~elData(span, 'object-id'),
-                                       value: span.textContent,
-                                       type: elData(span, 'type')
-                               });
-                       });
-                       
-                       return values;
-               },
-               
-               /**
-                * Sets the list of current values.
-                * 
-                * @param       {string}        elementId       input element id
-                * @param       {Array}         values          list of objects containing object id and value
-                */
-               setValues: function(elementId, values) {
-                       if (!_data.has(elementId)) {
-                               throw new Error("Element id '" + elementId + "' is unknown.");
-                       }
-                       
-                       var data = _data.get(elementId);
-                       
-                       // remove all existing items first
-                       var i, length;
-                       var items = DomTraverse.childrenByClass(data.list, 'item');
-                       for (i = 0, length = items.length; i < length; i++) {
-                               this._removeItem(null, items[i], true);
-                       }
-                       
-                       // add new items
-                       for (i = 0, length = values.length; i < length; i++) {
-                               this._addItem(elementId, values[i]);
-                       }
-               },
-               
-               /**
-                * Binds static event listeners.
-                */
-               _setup: function() {
-                       if (_didInit) {
-                               return;
-                       }
-                       
-                       _didInit = true;
-                       
-                       _callbackKeyDown = this._keyDown.bind(this);
-                       _callbackKeyPress = this._keyPress.bind(this);
-                       _callbackKeyUp = this._keyUp.bind(this);
-                       _callbackPaste = this._paste.bind(this);
-                       _callbackRemoveItem = this._removeItem.bind(this);
-                       _callbackBlur = this._blur.bind(this);
-               },
-               
-               /**
-                * Creates the DOM structure for target element. If `element` is a `<textarea>`
-                * it will be automatically replaced with an `<input>` element.
-                * 
-                * @param       {Element}       element         input element
-                * @param       {Object}        options         option list
-                */
-               _createUI: function(element, options) {
-                       var list = elCreate('ol');
-                       list.className = 'inputItemList' + (element.disabled ? ' disabled' : '');
-                       elData(list, 'element-id', element.id);
-                       list.addEventListener(WCF_CLICK_EVENT, function(event) {
-                               if (event.target === list) {
-                                       //noinspection JSUnresolvedFunction
-                                       element.focus();
-                               }
-                       });
-                       
-                       var listItem = elCreate('li');
-                       listItem.className = 'input';
-                       list.appendChild(listItem);
-                       
-                       element.addEventListener('keydown', _callbackKeyDown);
-                       element.addEventListener('keypress', _callbackKeyPress);
-                       element.addEventListener('keyup', _callbackKeyUp);
-                       element.addEventListener('paste', _callbackPaste);
-                       var hasFocus = element === document.activeElement;
-                       if (hasFocus) {
-                               //noinspection JSUnresolvedFunction
-                               element.blur();
-                       }
-                       element.addEventListener('blur', _callbackBlur);
-                       element.parentNode.insertBefore(list, element);
-                       listItem.appendChild(element);
-                       if (hasFocus) {
-                               window.setTimeout(function() {
-                                       //noinspection JSUnresolvedFunction
-                                       element.focus();
-                               }, 1);
-                       }
-                       
-                       if (options.maxLength !== -1) {
-                               elAttr(element, 'maxLength', options.maxLength);
-                       }
-                       
-                       var shadow = null, values = [];
-                       if (options.isCSV) {
-                               shadow = elCreate('input');
-                               shadow.className = 'itemListInputShadow';
-                               shadow.type = 'hidden';
-                               //noinspection JSUnresolvedVariable
-                               shadow.name = element.name;
-                               element.removeAttribute('name');
-                               
-                               list.parentNode.insertBefore(shadow, list);
-                               
-                               //noinspection JSUnresolvedVariable
-                               var value, tmp = element.value.split(',');
-                               for (var i = 0, length = tmp.length; i < length; i++) {
-                                       value = tmp[i].trim();
-                                       if (value.length) {
-                                               values.push(value);
-                                       }
-                               }
-                               
-                               if (element.nodeName === 'TEXTAREA') {
-                                       var inputElement = elCreate('input');
-                                       inputElement.type = 'text';
-                                       element.parentNode.insertBefore(inputElement, element);
-                                       inputElement.id = element.id;
-                                       
-                                       elRemove(element);
-                                       element = inputElement;
-                               }
-                       }
-                       
-                       return {
-                               element: element,
-                               list: list,
-                               shadow: shadow,
-                               values: values
-                       };
-               },
-               
-               /**
-                * Returns true if the input accepts new items.
-                * 
-                * @param       {string}        elementId       input element id
-                * @return      {boolean}       true if at least one more item can be added
-                * @protected
-                */
-               _acceptsNewItems: function (elementId) {
-                       var data = _data.get(elementId);
-                       if (data.options.maxItems === -1) {
-                               return true;
-                       }
-                       
-                       return (data.list.childElementCount - 1 < data.options.maxItems);
-               },
-               
-               /**
-                * Enforces the maximum number of items.
-                * 
-                * @param       {string}        elementId       input element id
-                */
-               _handleLimit: function(elementId) {
-                       var data = _data.get(elementId);
-                       if (this._acceptsNewItems(elementId)) {
-                               if (data.element.disabled) {
-                                       data.element.disabled = false;
-                                       data.element.removeAttribute('placeholder');
-                               }
-                       }
-                       else if (!data.element.disabled) {
-                               data.element.disabled = true;
-                               elAttr(data.element, 'placeholder', Language.get('wcf.global.form.input.maxItems'));
-                       }
-               },
-               
-               /**
-                * Sets the active item list id and handles keyboard access to remove an existing item.
-                * 
-                * @param       {object}        event           event object
-                */
-               _keyDown: function(event) {
-                       var input = event.currentTarget;
-                       var lastItem = input.parentNode.previousElementSibling;
-                       
-                       _activeId = input.id;
-                       
-                       if (event.keyCode === 8) {
-                               // 8 = [BACKSPACE]
-                               if (input.value.length === 0) {
-                                       if (lastItem !== null) {
-                                               if (lastItem.classList.contains('active')) {
-                                                       this._removeItem(null, lastItem);
-                                               }
-                                               else {
-                                                       lastItem.classList.add('active');
-                                               }
-                                       }
-                               }
-                       }
-                       else if (event.keyCode === 27) {
-                               // 27 = [ESC]
-                               if (lastItem !== null && lastItem.classList.contains('active')) {
-                                       lastItem.classList.remove('active');
-                               }
-                       }
-               },
-               
-               /**
-                * Handles the `[ENTER]` and `[,]` key to add an item to the list unless it is restricted.
-                * 
-                * @param       {Event}         event           event object
-                */
-               _keyPress: function(event) {
-                       if (EventKey.Enter(event) || EventKey.Comma(event)) {
-                               event.preventDefault();
-                               
-                               if (_data.get(event.currentTarget.id).options.restricted) {
-                                       // restricted item lists only allow results from the dropdown to be picked
-                                       return;
-                               }
-                               
-                               var value = event.currentTarget.value.trim();
-                               if (value.length) {
-                                       this._addItem(event.currentTarget.id, { objectId: 0, value: value });
-                               }
-                       }
-               },
-               
-               /**
-                * Splits comma-separated values being pasted into the input field.
-                * 
-                * @param       {Event}         event
-                * @protected
-                */
-               _paste: function (event) {
-                       var text = '';
-                       if (typeof window.clipboardData === 'object') {
-                               // IE11
-                               text = window.clipboardData.getData('Text');
-                       }
-                       else {
-                               text = event.clipboardData.getData('text/plain');
-                       }
-                       
-                       var element = event.currentTarget;
-                       var elementId = element.id;
-                       var maxLength = ~~elAttr(element, 'maxLength');
-                       
-                       text.split(/,/).forEach((function(item) {
-                               item = item.trim();
-                               if (maxLength && item.length > maxLength) {
-                                       // truncating items provides a better UX than throwing an error or silently discarding it
-                                       item = item.substr(0, maxLength);
-                               }
-                               
-                               if (item.length > 0 && this._acceptsNewItems(elementId)) {
-                                       this._addItem(elementId, {objectId: 0, value: item});
-                               }
-                       }).bind(this));
-                       
-                       event.preventDefault();
-               },
-               
-               /**
-                * Handles the keyup event to unmark an item for deletion.
-                * 
-                * @param       {object}        event           event object
-                */
-               _keyUp: function(event) {
-                       var input = event.currentTarget;
-                       
-                       if (input.value.length > 0) {
-                               var lastItem = input.parentNode.previousElementSibling;
-                               if (lastItem !== null) {
-                                       lastItem.classList.remove('active');
-                               }
-                       }
-               },
-               
-               /**
-                * Adds an item to the list.
-                * 
-                * @param       {string}        elementId       input element id
-                * @param       {object}        value           item value
-                */
-               _addItem: function(elementId, value) {
-                       var data = _data.get(elementId);
-                       
-                       var listItem = elCreate('li');
-                       listItem.className = 'item';
-                       
-                       var content = elCreate('span');
-                       content.className = 'content';
-                       elData(content, 'object-id', value.objectId);
-                       if (value.type) elData(content, 'type', value.type);
-                       content.textContent = value.value;
-                       listItem.appendChild(content);
-                       
-                       if (!data.element.disabled) {
-                               var button = elCreate('a');
-                               button.className = 'icon icon16 fa-times';
-                               button.addEventListener(WCF_CLICK_EVENT, _callbackRemoveItem);
-                               listItem.appendChild(button);
-                       }
-                       
-                       data.list.insertBefore(listItem, data.listItem);
-                       data.suggestion.addExcludedValue(value.value);
-                       data.element.value = '';
-                       
-                       if (!data.element.disabled) {
-                               this._handleLimit(elementId);
-                       }
-                       var values = this._syncShadow(data);
-                       
-                       if (typeof data.options.callbackChange === 'function') {
-                               if (values === null) values = this.getValues(elementId);
-                               data.options.callbackChange(elementId, values);
-                       }
-               },
-               
-               /**
-                * Removes an item from the list.
-                * 
-                * @param       {?object}       event           event object
-                * @param       {Element?}      item            list item
-                * @param       {boolean?}      noFocus         input element will not be focused if true
-                */
-               _removeItem: function(event, item, noFocus) {
-                       item = (event === null) ? item : event.currentTarget.parentNode;
-                       
-                       var parent = item.parentNode;
-                       //noinspection JSCheckFunctionSignatures
-                       var elementId = elData(parent, 'element-id');
-                       var data = _data.get(elementId);
-                       
-                       data.suggestion.removeExcludedValue(item.children[0].textContent);
-                       parent.removeChild(item);
-                       if (!noFocus) data.element.focus();
-                       
-                       this._handleLimit(elementId);
-                       var values = this._syncShadow(data);
-                       
-                       if (typeof data.options.callbackChange === 'function') {
-                               if (values === null) values = this.getValues(elementId);
-                               data.options.callbackChange(elementId, values);
-                       }
-               },
-               
-               /**
-                * Synchronizes the shadow input field with the current list item values.
-                * 
-                * @param       {object}        data            element data
-                */
-               _syncShadow: function(data) {
-                       if (!data.options.isCSV) return null;
-                       if (typeof data.options.callbackSyncShadow === 'function') {
-                               return data.options.callbackSyncShadow(data);
-                       }
-                       
-                       var value = '', values = this.getValues(data.element.id);
-                       for (var i = 0, length = values.length; i < length; i++) {
-                               value += (value.length ? ',' : '') + values[i].value;
-                       }
-                       
-                       data.shadow.value = value;
-                       
-                       return values;
-               },
-               
-               /**
-                * Handles the blur event.
-                *
-                * @param       {object}        event           event object
-                */
-               _blur: function(event) {
-                       var input = event.currentTarget;
-                       var data = _data.get(input.id);
-                       if (data.options.restricted) {
-                               // restricted item lists only allow results from the dropdown to be picked
-                               return;
-                       }
-                       
-                       var value = input.value.trim();
-                       if (value.length) {
-                               if (!data.suggestion || !data.suggestion.isActive()) {
-                                       this._addItem(input.id, { objectId: 0, value: value });
-                               }
-                       }
-               }
-       };
-});
-
-/**
- * Utility class to provide a 'Jump To' overlay. 
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Page/JumpTo
- */
-define('WoltLabSuite/Core/Ui/Page/JumpTo',['Language', 'ObjectMap', 'Ui/Dialog'], function(Language, ObjectMap, UiDialog) {
-       "use strict";
-       
-       var _activeElement = null;
-       var _buttonSubmit = null;
-       var _description = null;
-       var _elements = new ObjectMap();
-       var _input = null;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Page/JumpTo
-        */
-       var UiPageJumpTo = {
-               /**
-                * Initializes a 'Jump To' element.
-                * 
-                * @param       {Element}       element         trigger element
-                * @param       {function}      callback        callback function, receives the page number as first argument
-                */
-               init: function(element, callback) {
-                       callback = callback || null;
-                       if (callback === null) {
-                               var redirectUrl = elData(element, 'link');
-                               if (redirectUrl) {
-                                       callback = function(pageNo) {
-                                               window.location = redirectUrl.replace(/pageNo=%d/, 'pageNo=' + pageNo);
-                                       };
-                               }
-                               else {
-                                       callback = function() {};
-                               }
-                               
-                       }
-                       else if (typeof callback !== 'function') {
-                               throw new TypeError("Expected a valid function for parameter 'callback'.");
-                       }
-                       
-                       if (!_elements.has(element)) {
-                               elBySelAll('.jumpTo', element, (function(jumpTo) {
-                                       jumpTo.addEventListener(WCF_CLICK_EVENT, this._click.bind(this, element));
-                                       _elements.set(element, { callback: callback });
-                               }).bind(this));
-                       }
-               },
-               
-               /**
-                * Handles clicks on the trigger element.
-                * 
-                * @param       {Element}       element         trigger element
-                * @param       {object}        event           event object
-                */
-               _click: function(element, event) {
-                       _activeElement = element;
-                       
-                       if (typeof event === 'object') {
-                               event.preventDefault();
-                       }
-                       
-                       UiDialog.open(this);
-                       
-                       var pages = elData(element, 'pages');
-                       _input.value = pages;
-                       _input.setAttribute('max', pages);
-                       _input.select();
-                       
-                       _description.textContent = Language.get('wcf.page.jumpTo.description').replace(/#pages#/, pages);
-               },
-               
-               /**
-                * Handles changes to the page number input field.
-                * 
-                * @param       {object}        event           event object
-                */
-               _keyUp: function(event) {
-                       if (event.which === 13 && _buttonSubmit.disabled === false) {
-                               this._submit();
-                               return;
-                       }
-                       
-                       var pageNo = ~~_input.value;
-                       if (pageNo < 1 || pageNo > ~~elAttr(_input, 'max')) {
-                               _buttonSubmit.disabled = true;
-                       }
-                       else {
-                               _buttonSubmit.disabled = false;
-                       }
-               },
-               
-               /**
-                * Invokes the callback with the chosen page number as first argument.
-                * 
-                * @param       {object}        event           event object
-                */
-               _submit: function(event) {
-                       _elements.get(_activeElement).callback(~~_input.value);
-                       
-                       UiDialog.close(this);
-               },
-               
-               _dialogSetup: function() {
-                       var source = '<dl>'
-                                       + '<dt><label for="jsPaginationPageNo">' + Language.get('wcf.page.jumpTo') + '</label></dt>'
-                                       + '<dd>'
-                                               + '<input type="number" id="jsPaginationPageNo" value="1" min="1" max="1" class="tiny">'
-                                               + '<small></small>'
-                                       + '</dd>'
-                               + '</dl>'
-                               + '<div class="formSubmit">'
-                                       + '<button class="buttonPrimary">' + Language.get('wcf.global.button.submit') + '</button>'
-                               + '</div>';
-                       
-                       return {
-                               id: 'paginationOverlay',
-                               options: {
-                                       onSetup: (function(content) {
-                                               _input = elByTag('input', content)[0];
-                                               _input.addEventListener('keyup', this._keyUp.bind(this));
-                                               
-                                               _description = elByTag('small', content)[0];
-                                               
-                                               _buttonSubmit = elByTag('button', content)[0];
-                                               _buttonSubmit.addEventListener(WCF_CLICK_EVENT, this._submit.bind(this));
-                                       }).bind(this),
-                                       title: Language.get('wcf.global.page.pagination')
-                               },
-                               source: source
-                       };
-               }
-       };
-       
-       return UiPageJumpTo;
-});
-/**
- * Callback-based pagination.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Pagination
- */
-define('WoltLabSuite/Core/Ui/Pagination',['Core', 'Language', 'ObjectMap', 'StringUtil', 'WoltLabSuite/Core/Ui/Page/JumpTo'], function(Core, Language, ObjectMap, StringUtil, UiPageJumpTo) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function UiPagination(element, options) { this.init(element, options); }
-       UiPagination.prototype = {
-               /**
-                * maximum number of displayed page links, should match the PHP implementation
-                * @var {int}
-                */
-               SHOW_LINKS: 11,
-               
-               /**
-                * Initializes the pagination.
-                * 
-                * @param       {Element}       element         container element
-                * @param       {object}        options         list of initialization options
-                */
-               init: function(element, options) {
-                       this._element = element;
-                       this._options = Core.extend({
-                               activePage: 1,
-                               maxPage: 1,
-                               
-                               callbackShouldSwitch: null,
-                               callbackSwitch: null
-                       }, options);
-                       
-                       if (typeof this._options.callbackShouldSwitch !== 'function') this._options.callbackShouldSwitch = null;
-                       if (typeof this._options.callbackSwitch !== 'function') this._options.callbackSwitch = null;
-                       
-                       this._element.classList.add('pagination');
-                       
-                       this._rebuild(this._element);
-               },
-               
-               /**
-                * Rebuilds the entire pagination UI.
-                */
-               _rebuild: function() {
-                       var hasHiddenPages = false;
-                       
-                       // clear content
-                       this._element.innerHTML = '';
-                       
-                       var list = elCreate('ul'), link;
-                       
-                       var listItem = elCreate('li');
-                       listItem.className = 'skip';
-                       list.appendChild(listItem);
-                       
-                       var iconClassNames = 'icon icon24 fa-chevron-left';
-                       if (this._options.activePage > 1) {
-                               link = elCreate('a');
-                               link.className = iconClassNames + ' jsTooltip';
-                               link.href = '#';
-                               link.title = Language.get('wcf.global.page.previous');
-                               link.rel = 'prev';
-                               listItem.appendChild(link);
-                               
-                               link.addEventListener(WCF_CLICK_EVENT, this.switchPage.bind(this, this._options.activePage - 1));
-                       }
-                       else {
-                               listItem.innerHTML = '<span class="' + iconClassNames + '"></span>';
-                               listItem.classList.add('disabled');
-                       }
-                       
-                       // add first page
-                       list.appendChild(this._createLink(1));
-                       
-                       // calculate page links
-                       var maxLinks = this.SHOW_LINKS - 4;
-                       var linksBefore = this._options.activePage - 2;
-                       if (linksBefore < 0) linksBefore = 0;
-                       var linksAfter = this._options.maxPage - (this._options.activePage + 1);
-                       if (linksAfter < 0) linksAfter = 0;
-                       if (this._options.activePage > 1 && this._options.activePage < this._options.maxPage) maxLinks--;
-                       
-                       var half = maxLinks / 2;
-                       var left = this._options.activePage;
-                       var right = this._options.activePage;
-                       if (left < 1) left = 1;
-                       if (right < 1) right = 1;
-                       if (right > this._options.maxPage - 1) right = this._options.maxPage - 1;
-                       
-                       if (linksBefore >= half) {
-                               left -= half;
-                       }
-                       else {
-                               left -= linksBefore;
-                               right += half - linksBefore;
-                       }
-                       
-                       if (linksAfter >= half) {
-                               right += half;
-                       }
-                       else {
-                               right += linksAfter;
-                               left -= half - linksAfter;
-                       }
-                       
-                       right = Math.ceil(right);
-                       left = Math.ceil(left);
-                       if (left < 1) left = 1;
-                       if (right > this._options.maxPage) right = this._options.maxPage;
-                       
-                       // left ... links
-                       var jumpToHtml = '<a class="jsTooltip" title="' + Language.get('wcf.page.jumpTo') + '">&hellip;</a>';
-                       if (left > 1) {
-                               if (left - 1 < 2) {
-                                       list.appendChild(this._createLink(2));
-                               }
-                               else {
-                                       listItem = elCreate('li');
-                                       listItem.className = 'jumpTo';
-                                       listItem.innerHTML = jumpToHtml;
-                                       list.appendChild(listItem);
-                                       
-                                       hasHiddenPages = true;
-                               }
-                       }
-                       
-                       // visible links
-                       for (var i = left + 1; i < right; i++) {
-                               list.appendChild(this._createLink(i));
-                       }
-                       
-                       // right ... links
-                       if (right < this._options.maxPage) {
-                               if (this._options.maxPage - right < 2) {
-                                       list.appendChild(this._createLink(this._options.maxPage - 1));
-                               }
-                               else {
-                                       listItem = elCreate('li');
-                                       listItem.className = 'jumpTo';
-                                       listItem.innerHTML = jumpToHtml;
-                                       list.appendChild(listItem);
-                                       
-                                       hasHiddenPages = true;
-                               }
-                       }
-                       
-                       // add last page
-                       list.appendChild(this._createLink(this._options.maxPage));
-                       
-                       // add next button
-                       listItem = elCreate('li');
-                       listItem.className = 'skip';
-                       list.appendChild(listItem);
-                       
-                       iconClassNames = 'icon icon24 fa-chevron-right';
-                       if (this._options.activePage < this._options.maxPage) {
-                               link = elCreate('a');
-                               link.className = iconClassNames + ' jsTooltip';
-                               link.href = '#';
-                               link.title = Language.get('wcf.global.page.next');
-                               link.rel = 'next';
-                               listItem.appendChild(link);
-                               
-                               link.addEventListener(WCF_CLICK_EVENT, this.switchPage.bind(this, this._options.activePage + 1));
-                       }
-                       else {
-                               listItem.innerHTML = '<span class="' + iconClassNames + '"></span>';
-                               listItem.classList.add('disabled');
-                       }
-                       
-                       if (hasHiddenPages) {
-                               elData(list, 'pages', this._options.maxPage);
-                               
-                               UiPageJumpTo.init(list, this.switchPage.bind(this));
-                       }
-                       
-                       this._element.appendChild(list);
-               },
-               
-               /**
-                * Creates a link to a specific page.
-                * 
-                * @param       {int}           pageNo          page number
-                * @return      {Element}       link element
-                */
-               _createLink: function(pageNo) {
-                       var listItem = elCreate('li');
-                       if (pageNo !== this._options.activePage) {
-                               var link = elCreate('a');
-                               link.textContent = StringUtil.addThousandsSeparator(pageNo);
-                               link.addEventListener(WCF_CLICK_EVENT, this.switchPage.bind(this, pageNo));
-                               listItem.appendChild(link);
-                       }
-                       else {
-                               listItem.classList.add('active');
-                               listItem.innerHTML = '<span>' + StringUtil.addThousandsSeparator(pageNo) + '</span><span class="invisible">' + Language.get('wcf.page.pagePosition', { pageNo: pageNo, pages: this._options.maxPage }) + '</span>';
-                       }
-                       
-                       return listItem;
-               },
-               
-               /**
-                * Returns the active page.
-                *
-                * @return      {integer}
-                */
-               getActivePage: function() {
-                       return this._options.activePage;
-               },
-               
-               /**
-                * Returns the pagination Ui element.
-                * 
-                * @return      {HTMLElement}
-                */
-               getElement: function() {
-                       return this._element;
-               },
-               
-               /**
-                * Returns the maximum page.
-                * 
-                * @return      {integer}
-                */
-               getMaxPage: function() {
-                       return this._options.maxPage;
-               },
-               
-               /**
-                * Switches to given page number.
-                * 
-                * @param       {int}           pageNo          page number
-                * @param       {object}        event           event object
-                */
-               switchPage: function(pageNo, event) {
-                       if (typeof event === 'object') {
-                               event.preventDefault();
-                               
-                               // force tooltip to vanish and strip positioning
-                               if (event.currentTarget && elData(event.currentTarget, 'tooltip')) {
-                                       var tooltip = elById('balloonTooltip');
-                                       if (tooltip) {
-                                               Core.triggerEvent(event.currentTarget, 'mouseleave');
-                                               tooltip.style.removeProperty('top');
-                                               tooltip.style.removeProperty('bottom');
-                                       }
-                               }
-                       }
-                       
-                       pageNo = ~~pageNo;
-                       
-                       if (pageNo > 0 && this._options.activePage !== pageNo && pageNo <= this._options.maxPage) {
-                               if (this._options.callbackShouldSwitch !== null) {
-                                       if (this._options.callbackShouldSwitch(pageNo) !== true) {
-                                               return;
-                                       }
-                               }
-                               
-                               this._options.activePage = pageNo;
-                               this._rebuild();
-                               
-                               if (this._options.callbackSwitch !== null) {
-                                       this._options.callbackSwitch(pageNo);
-                               }
-                       }
-               }
-       };
-       
-       return UiPagination;
-});
-
-/**
- * Smoothly scrolls to an element while accounting for potential sticky headers.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Scroll
- */
-define('WoltLabSuite/Core/Ui/Scroll',['Dom/Util'], function(DomUtil) {
-       "use strict";
-       
-       var _callback = null;
-       var _callbackScroll = null;
-       var _offset = null;
-       var _timeoutScroll = null;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Scroll
-        */
-       return {
-               /**
-                * Scrolls to target element, optionally invoking the provided callback once scrolling has ended.
-                * 
-                * @param       {Element}       element         target element
-                * @param       {function=}     callback        callback invoked once scrolling has ended
-                */
-               element: function(element, callback) {
-                       if (!(element instanceof Element)) {
-                               throw new TypeError("Expected a valid DOM element.");
-                       }
-                       else if (callback !== undefined && typeof callback !== 'function') {
-                               throw new TypeError("Expected a valid callback function.");
-                       }
-                       else if (!document.body.contains(element)) {
-                               throw new Error("Element must be part of the visible DOM.");
-                       }
-                       else if (_callback !== null) {
-                               throw new Error("Cannot scroll to element, a concurrent request is running.");
-                       }
-                       
-                       if (callback) {
-                               _callback = callback;
-                               
-                               if (_callbackScroll === null) {
-                                       _callbackScroll = this._onScroll.bind(this);
-                               }
-                               
-                               window.addEventListener('scroll', _callbackScroll);
-                       }
-                       
-                       var y = DomUtil.offset(element).top;
-                       if (_offset === null) {
-                               _offset = 50;
-                               var pageHeader = elById('pageHeaderPanel');
-                               if (pageHeader !== null) {
-                                       var position = window.getComputedStyle(pageHeader).position;
-                                       if (position === 'fixed' || position === 'static') {
-                                               _offset = pageHeader.offsetHeight;
-                                       }
-                                       else {
-                                               _offset = 0;
-                                       }
-                               }
-                       }
-                       
-                       if (_offset > 0) {
-                               if (y <= _offset) {
-                                       y = 0;
-                               }
-                               else {
-                                       // add an offset to account for a sticky header
-                                       y -= _offset;
-                               }
-                       }
-                       
-                       var offset = window.pageYOffset;
-                       
-                       window.scrollTo({
-                               left: 0,
-                               top: y,
-                               behavior: 'smooth'
-                       });
-                       
-                       window.setTimeout((function () {
-                               // no scrolling took place
-                               if (offset === window.pageYOffset) {
-                                       this._onScroll();
-                               }
-                       }).bind(this), 100);
-               },
-               
-               /**
-                * Monitors scroll event to only execute the callback once scrolling has ended.
-                * 
-                * @protected
-                */
-               _onScroll: function() {
-                       if (_timeoutScroll !== null) window.clearTimeout(_timeoutScroll);
-                       
-                       _timeoutScroll = window.setTimeout(function() {
-                               if (_callback !== null) _callback();
-                               
-                               window.removeEventListener('scroll', _callbackScroll);
-                               _callback = null;
-                               _timeoutScroll = null;
-                       }, 100);
-               }
-       };
-});
-
-/**
- * Initializes modules required for media list view.
- *
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Controller/Media/List
- */
-define('WoltLabSuite/Core/Controller/Media/List',[
-               'Dom/ChangeListener',
-               'EventHandler',
-               'WoltLabSuite/Core/Controller/Clipboard',
-               'WoltLabSuite/Core/Media/Clipboard',
-               'WoltLabSuite/Core/Media/Editor',
-               'WoltLabSuite/Core/Media/List/Upload'
-       ],
-       function(
-               DomChangeListener,
-               EventHandler,
-               Clipboard,
-               MediaClipboard,
-               MediaEditor,
-               MediaListUpload
-       ) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _addButtonEventListeners: function() {},
-                       _deleteCallback: function() {},
-                       _deleteMedia: function(mediaIds) {},
-                       _edit: function() {}
-               };
-               return Fake;
-       }
-       
-       var _mediaEditor;
-       var _tableBody = elById('mediaListTableBody');
-       var _clipboardObjectIds = [];
-       var _upload;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Controller/Media/List
-        */
-       return {
-               init: function(options) {
-                       options = options || {};
-                       _upload = new MediaListUpload('uploadButton', 'mediaListTableBody', {
-                               categoryId: options.categoryId,
-                               multiple: true
-                       });
-                       
-                       MediaClipboard.init(
-                               'wcf\\acp\\page\\MediaListPage',
-                               options.hasMarkedItems || false,
-                               this
-                       );
-                       
-                       EventHandler.add('com.woltlab.wcf.media.upload', 'removedErroneousUploadRow', this._deleteCallback.bind(this));
-                       
-                       var deleteAction = new WCF.Action.Delete('wcf\\data\\media\\MediaAction', '.jsMediaRow');
-                       deleteAction.setCallback(this._deleteCallback);
-                       
-                       _mediaEditor = new MediaEditor({
-                               _editorSuccess: function(media, oldCategoryId) {
-                                       if (media.categoryID != oldCategoryId) {
-                                               window.setTimeout(function() {
-                                                       window.location.reload();
-                                               }, 500);
-                                       }
-                               }
-                       });
-                       
-                       this._addButtonEventListeners();
-                       
-                       DomChangeListener.add('WoltLabSuite/Core/Controller/Media/List', this._addButtonEventListeners.bind(this));
-                       
-                       EventHandler.add('com.woltlab.wcf.media.upload', 'success', this._openEditorAfterUpload.bind(this));
-               },
-               
-               /**
-                * Adds the `click` event listeners to the media edit icons in
-                * new media table rows.
-                */
-               _addButtonEventListeners: function() {
-                       var buttons = elByClass('jsMediaEditButton', _tableBody), button;
-                       while (buttons.length) {
-                               button = buttons[0];
-                               button.classList.remove('jsMediaEditButton');
-                               button.addEventListener(WCF_CLICK_EVENT, this._edit.bind(this));
-                       }
-               },
-               
-               /**
-                * Is triggered after media files have been deleted using the delete icon.
-                * 
-                * @param       {int[]?}        objectIds
-                */
-               _deleteCallback: function(objectIds) {
-                       var tableRowCount = elByTag('tr', _tableBody).length;
-                       if (objectIds.length === undefined) {
-                               if (!tableRowCount) {
-                                       window.location.reload();
-                               }
-                       }
-                       else if (objectIds.length === tableRowCount) {
-                               // table is empty, reload page
-                               window.location.reload();
-                       }
-                       else {
-                               Clipboard.reload.bind(Clipboard)
-                       }
-               },
-               
-               /**
-                * Is called when a media edit icon is clicked.
-                * 
-                * @param       {Event}         event
-                */
-               _edit: function(event) {
-                       _mediaEditor.edit(elData(event.currentTarget, 'object-id'));
-               },
-               
-               /**
-                * Opens the media editor after uploading a single file.
-                *
-                * @param       {object}        data    upload event data
-                * @since       5.2
-                */
-               _openEditorAfterUpload: function(data) {
-                       if (data.upload === _upload && !data.isMultiFileUpload && !_upload.hasPendingUploads()) {
-                               var keys = Object.keys(data.media);
-                               
-                               if (keys.length) {
-                                       _mediaEditor.edit(this._media.get(~~data.media[keys[0]].mediaID));
-                               }
-                       }
-               },
-               
-               /**
-                * Is called after the media files with the given ids have been deleted via clipboard.
-                * 
-                * @param       {int[]}         mediaIds        ids of deleted media files
-                */
-               clipboardDeleteMedia: function(mediaIds) {
-                       var mediaRows = elByClass('jsMediaRow');
-                       for (var i = 0; i < mediaRows.length; i++) {
-                               var media = mediaRows[i];
-                               var mediaID = ~~elData(elByClass('jsClipboardItem', media)[0], 'object-id');
-                               
-                               if (mediaIds.indexOf(mediaID) !== -1) {
-                                       elRemove(media);
-                                       i--;
-                               }
-                       }
-                       
-                       if (!mediaRows.length) {
-                               window.location.reload();
-                       }
-               }
-       }
-});
-/**
- * Handles dismissible user notices.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Controller/Notice/Dismiss
- */
-define('WoltLabSuite/Core/Controller/Notice/Dismiss',['Ajax'], function(Ajax) {
-       "use strict";
-       
-       /**
-        * @exports     WoltLabSuite/Core/Controller/Notice/Dismiss
-        */
-       var ControllerNoticeDismiss = {
-               /**
-                * Initializes dismiss buttons.
-                */
-               setup: function() {
-                       var buttons = elByClass('jsDismissNoticeButton');
-                       
-                       if (buttons.length) {
-                               var clickCallback = this._click.bind(this);
-                               for (var i = 0, length = buttons.length; i < length; i++) {
-                                       buttons[i].addEventListener(WCF_CLICK_EVENT, clickCallback);
-                               }
-                       }
-               },
-               
-               /**
-                * Sends a request to dismiss a notice and removes it afterwards.
-                */
-               _click: function(event) {
-                       var button = event.currentTarget;
-                       
-                       Ajax.apiOnce({
-                               data: {
-                                       actionName: 'dismiss',
-                                       className: 'wcf\\data\\notice\\NoticeAction',
-                                       objectIDs: [ elData(button, 'object-id') ]
-                               },
-                               success: function() {
-                                       elRemove(button.parentNode);
-                               }
-                       });
-               }
-       };
-       
-       return ControllerNoticeDismiss;
-});
-
-/**
- * Manages form field dependencies.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Dependency/Manager
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Dependency/Manager',['Dictionary', 'Dom/ChangeListener', 'EventHandler', 'List', 'Dom/Traverse', 'Dom/Util', 'ObjectMap'], function(Dictionary, DomChangeListener, EventHandler, List, DomTraverse, DomUtil, ObjectMap) {
-       "use strict";
-       
-       /**
-        * is `true` if containters are currently checked for their availablility, otherwise `false`
-        * @type        {boolean}
-        * @private
-        */
-       var _checkingContainers = false;
-       
-       /**
-        * is `true` if containter will be checked again after the current check for their availablility
-        * has finished, otherwise `false`
-        * @type        {boolean}
-        * @private
-        */
-       var _checkContainersAgain = true;
-       
-       /**
-        * list of containers hidden due to their own dependencies
-        * @type        {List}
-        * @private
-        */
-       var _dependencyHiddenNodes = new List();
-       
-       /**
-        * list of fields for which event listeners have been registered
-        * @type        {Dictionary}
-        * @private
-        */
-       var _fields = new Dictionary();
-       
-       /**
-        * list of registered forms
-        * @type        {List}
-        * @private
-        */
-       var _forms = new List();
-       
-       /**
-        * list of dependencies grouped by the dependent node they belong to
-        * @type        {Dictionary}
-        * @private
-        */
-       var _nodeDependencies = new Dictionary();
-       
-       /**
-        * cache of validation-related properties of hidden form fields
-        * @type        {ObjectMap}
-        * @private
-        */
-       var _validatedFieldProperties = new ObjectMap();
-       
-       return {
-               /**
-                * Hides the given node because of its own dependencies.
-                * 
-                * @param       {HTMLElement}   node    hidden node
-                * @protected
-                */
-               _hide: function(node) {
-                       elHide(node);
-                       _dependencyHiddenNodes.add(node);
-                       
-                       // also hide tab menu entry
-                       if (node.classList.contains('tabMenuContent')) {
-                               elBySelAll('li', DomTraverse.prevByClass(node, 'tabMenu'), function(tabLink) {
-                                       if (elData(tabLink, 'name') === elData(node, 'name')) {
-                                               elHide(tabLink);
-                                       }
-                               });
-                       }
-                       
-                       elBySelAll('[max], [maxlength], [min], [required]', node, function(validatedField) {
-                               var properties = new Dictionary();
-                               
-                               var max = elAttr(validatedField, 'max');
-                               if (max) {
-                                       properties.set('max', max);
-                                       validatedField.removeAttribute('max');
-                               }
-                               
-                               var maxlength = elAttr(validatedField, 'maxlength');
-                               if (maxlength) {
-                                       properties.set('maxlength', maxlength);
-                                       validatedField.removeAttribute('maxlength');
-                               }
-                               
-                               var min = elAttr(validatedField, 'min');
-                               if (min) {
-                                       properties.set('min', min);
-                                       validatedField.removeAttribute('min');
-                               }
-                               
-                               if (validatedField.required) {
-                                       properties.set('required', true);
-                                       validatedField.removeAttribute('required');
-                               }
-                               
-                               _validatedFieldProperties.set(validatedField, properties);
-                       });
-               },
-               
-               /**
-                * Shows the given node because of its own dependencies.
-                * 
-                * @param       {HTMLElement}   node    shown node
-                * @protected
-                */
-               _show: function(node) {
-                       elShow(node);
-                       _dependencyHiddenNodes.delete(node);
-                       
-                       // also show tab menu entry
-                       if (node.classList.contains('tabMenuContent')) {
-                               elBySelAll('li', DomTraverse.prevByClass(node, 'tabMenu'), function(tabLink) {
-                                       if (elData(tabLink, 'name') === elData(node, 'name')) {
-                                               elShow(tabLink);
-                                       }
-                               });
-                       }
-                       
-                       elBySelAll('input, select', node, function(validatedField) {
-                               // if a container is shown, ignore all fields that
-                               // have a hidden parent element within the container
-                               var parentNode = validatedField.parentNode;
-                               while (parentNode !== node && parentNode.style.getPropertyValue('display') !== 'none') {
-                                       parentNode = parentNode.parentNode;
-                               }
-                               
-                               if (parentNode === node && _validatedFieldProperties.has(validatedField)) {
-                                       var properties = _validatedFieldProperties.get(validatedField);
-                                       
-                                       if (properties.has('max')) {
-                                               elAttr(validatedField, 'max', properties.get('max'));
-                                       }
-                                       if (properties.has('maxlength')) {
-                                               elAttr(validatedField, 'maxlength', properties.get('maxlength'));
-                                       }
-                                       if (properties.has('min')) {
-                                               elAttr(validatedField, 'min', properties.get('min'));
-                                       }
-                                       if (properties.has('required')) {
-                                               elAttr(validatedField, 'required', '');
-                                       }
-                                       
-                                       _validatedFieldProperties.delete(validatedField);
-                               }
-                       });
-               },
-               
-               /**
-                * Registers a new form field dependency.
-                * 
-                * @param       {WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract}      dependency      new dependency
-                */
-               addDependency: function(dependency) {
-                       var dependentNode = dependency.getDependentNode();
-                       if (!_nodeDependencies.has(dependentNode.id)) {
-                               _nodeDependencies.set(dependentNode.id, [dependency]);
-                       }
-                       else {
-                               _nodeDependencies.get(dependentNode.id).push(dependency);
-                       }
-                       
-                       var fields = dependency.getFields();
-                       for (var i = 0, length = fields.length; i < length; i++) {
-                               var field = fields[i];
-                               var id = DomUtil.identify(field);
-                               
-                               if (!_fields.has(id)) {
-                                       _fields.set(id, field);
-                                       
-                                       if (field.tagName === 'INPUT' && (field.type === 'checkbox' || field.type === 'radio')) {
-                                               field.addEventListener('change', this.checkDependencies.bind(this));
-                                       }
-                                       else {
-                                               field.addEventListener('input', this.checkDependencies.bind(this));
-                                       }
-                               }
-                       }
-               },
-               
-               /**
-                * Checks if all dependencies are met.
-                */
-               checkDependencies: function() {
-                       var obsoleteNodes = [];
-                       
-                       _nodeDependencies.forEach(function(nodeDependencies, nodeId) {
-                               var dependentNode = elById(nodeId);
-                               
-                               // check if dependent node still exists
-                               if (dependentNode === null) {
-                                       obsoleteNodes.push(dependentNode);
-                                       return;
-                               }
-                               
-                               for (var i = 0, length = nodeDependencies.length; i < length; i++) {
-                                       // if any dependency is not met, hide the element
-                                       if (!nodeDependencies[i].checkDependency()) {
-                                               this._hide(dependentNode);
-                                               return;
-                                       }
-                               }
-                               
-                               // all node dependency is met
-                               this._show(dependentNode);
-                       }.bind(this));
-                       
-                       // delete dependencies for removed elements
-                       for (var i = 0, length = obsoleteNodes.length; i < length; i++) {
-                               _nodeDependencies.delete(obsoleteNodes.id);
-                       }
-                       
-                       this.checkContainers();
-               },
-               
-               /**
-                * Adds the given callback to the list of callbacks called when checking containers.
-                * 
-                * @param       {function}      callback
-                */
-               addContainerCheckCallback: function(callback) {
-                       if (typeof callback !== 'function') {
-                               throw new TypeError("Expected a valid callback for parameter 'callback'.");
-                       }
-                       
-                       EventHandler.add('com.woltlab.wcf.form.builder.dependency', 'checkContainers', callback);
-               },
-               
-               /**
-                * Checks the containers for their availability.
-                * 
-                * If this function is called while containers are currently checked, the containers
-                * will be checked after the current check has been finished completely.
-                */
-               checkContainers: function() {
-                       // check if containers are currently being checked
-                       if (_checkingContainers === true) {
-                               // and if that is the case, calling this method indicates, that after the current round,
-                               // containters should be checked to properly propagate changes in children to their parents
-                               _checkContainersAgain = true;
-                               
-                               return;
-                       }
-                       
-                       // starting to check containers also resets the flag to check containers again after the current check 
-                       _checkingContainers = true;
-                       _checkContainersAgain = false;
-                       
-                       EventHandler.fire('com.woltlab.wcf.form.builder.dependency', 'checkContainers');
-                       
-                       // finish checking containers and check if containters should be checked again
-                       _checkingContainers = false;
-                       if (_checkContainersAgain) {
-                               this.checkContainers();
-                       }
-               },
-               
-               /**
-                * Returns `true` if the given node has been hidden because of its own dependencies.
-                * 
-                * @param       {HTMLElement}   node    checked node
-                * @return      {boolean}
-                */
-               isHiddenByDependencies: function(node) {
-                       if (_dependencyHiddenNodes.has(node)) {
-                               return true;
-                       }
-                       
-                       var returnValue = false;
-                       _dependencyHiddenNodes.forEach(function(hiddenNode) {
-                               if (DomUtil.contains(hiddenNode, node)) {
-                                       returnValue = true;
-                               }
-                       });
-                       
-                       return returnValue;
-               },
-               
-               /**
-                * Registers the form with the given id with the dependency manager.
-                * 
-                * @param       {string}        formId          id of register form
-                * @throws      {Error}                         if given form id is invalid or has already been registered
-                */
-               register: function(formId) {
-                       var form = elById(formId);
-                       
-                       if (form === null) {
-                               throw new Error("Unknown element with id '" + formId + "'");
-                       }
-                       
-                       if (_forms.has(form)) {
-                               throw new Error("Form with id '" + formId + "' has already been registered.");
-                       }
-                       
-                       _forms.add(form);
-               },
-               
-               /**
-                * Unregisters the form with the given id and all of its dependencies.
-                * 
-                * @param       {string}        formId          id of unregistered form
-                */
-               unregister: function(formId) {
-                       var form = elById(formId);
-                       
-                       if (form === null) {
-                               throw new Error("Unknown element with id '" + formId + "'");
-                       }
-                       
-                       if (!_forms.has(form)) {
-                               throw new Error("Form with id '" + formId + "' has not been registered.");
-                       }
-                       
-                       _forms.delete(form);
-                       
-                       _dependencyHiddenNodes.forEach(function(hiddenNode) {
-                               if (form.contains(hiddenNode)) {
-                                       _dependencyHiddenNodes.delete(hiddenNode);
-                               }
-                       });
-                       _nodeDependencies.forEach(function(dependencies, nodeId) {
-                               if (form.contains(elById(nodeId))) {
-                                       _nodeDependencies.delete(nodeId);
-                               }
-                               
-                               for (var i = 0, length = dependencies.length; i < length; i++) {
-                                       var fields = dependencies[i].getFields();
-                                       for (var j = 0, length = fields.length; j < length; j++) {
-                                               var field = fields[j];
-                                               
-                                               _fields.delete(field.id);
-                                               
-                                               _validatedFieldProperties.delete(field);
-                                       }
-                               }
-                       });
-               }
-       };
-});
-
-/**
- * Data handler for a form builder field in an Ajax form.
- *
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Field
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Field',[], function() {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function FormBuilderField(fieldId) {
-               this.init(fieldId);
-       };
-       FormBuilderField.prototype = {
-               /**
-                * Initializes the form field.
-                * 
-                * @param       {string}        fieldId         id of the relevant form builder field
-                */
-               init: function(fieldId) {
-                       this._fieldId = fieldId;
-                       
-                       this._readField();
-               },
-               
-               /**
-                * Returns the current data of the field or a promise returning the current data
-                * of the field.
-                * 
-                * @return      {Promise|data}
-                */
-               _getData: function() {
-                       throw new Error("Missing implementation of WoltLabSuite/Core/Form/Builder/Field/Field._getData!");
-               },
-               
-               /**
-                * Reads the field HTML element.
-                */
-               _readField: function() {
-                       this._field = elById(this._fieldId);
-                       
-                       if (this._field === null) {
-                               throw new Error("Unknown field with id '" + this._fieldId + "'.");
-                       }
-               },
-               
-               /**
-                * Destroys the field.
-                * 
-                * This function is useful for remove registered elements from other APIs like dialogs.
-                */
-               destroy: function() {
-                       // does nothing
-               },
-               
-               /**
-                * Returns a promise returning the current data of the field.
-                * 
-                * @return      {Promise}
-                */
-               getData: function() {
-                       return Promise.resolve(this._getData());
-               },
-               
-               /**
-                * Returns the id of the field.
-                * 
-                * @return      {string}
-                */
-               getId: function() {
-                       return this._fieldId;
-               }
-       };
-       
-       return FormBuilderField;
-});
-
-/**
- * Manager for registered Ajax forms and its fields that can be used to retrieve the current data
- * of the registered forms.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Manager
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Manager',[
-       'Core',
-       'Dictionary',
-       './Field/Dependency/Manager',
-       './Field/Field'
-], function(
-       Core,
-       Dictionary,
-       FormBuilderFieldDependencyManager,
-       FormBuilderField
-) {
-       "use strict";
-       
-       var _fields = new Dictionary();
-       var _forms = new Dictionary();
-       
-       return {
-               /**
-                * Returns a promise returning the data of the form with the given id.
-                * 
-                * @param       {string}        formId
-                * @return      {Promise}
-                */
-               getData: function(formId) {
-                       if (!this.hasForm(formId)) {
-                               throw new Error("Unknown form with id '" + formId + "'.");
-                       }
-                       
-                       var promises = [];
-                       
-                       _fields.get(formId).forEach(function(field) {
-                               var fieldData = field.getData();
-                               
-                               if (!(fieldData instanceof Promise)) {
-                                       throw new TypeError("Data for field with id '" + field.getId() + "' is no promise.");
-                               }
-                               
-                               promises.push(fieldData);
-                       });
-                       
-                       return Promise.all(promises).then(function(promiseData) {
-                               var data = {};
-                               
-                               for (var i = 0, length = promiseData.length; i < length; i++) {
-                                       data = Core.extend(data, promiseData[i]);
-                               }
-                               
-                               return data;
-                       });
-               },
-               
-               /**
-                * Returns the registered form with given id.
-                * 
-                * @param       {string}        formId
-                * @return      {HTMLElement}
-                */
-               getForm: function(formId) {
-                       if (!this.hasForm(formId)) {
-                               throw new Error("Unknown form with id '" + formId + "'.");
-                       }
-                       
-                       return _forms.get(formId);
-               },
-               
-               /**
-                * Returns `true` if a field with the given id has been registered for the form with
-                * the given id and `false` otherwise.
-                * 
-                * @param       {string}        formId
-                * @param       {string}        fieldId
-                * @return      {boolean}
-                */
-               hasField: function(formId, fieldId) {
-                       if (!this.hasForm(formId)) {
-                               throw new Error("Unknown form with id '" + formId + "'.");
-                       }
-                       
-                       return _fields.get(formId).has(fieldId);
-               },
-               
-               /**
-                * Returns `true` if a form with the given id has been registered and `false`
-                * otherwise.
-                * 
-                * @param       {string}        formId
-                * @return      {boolean}
-                */
-               hasForm: function(formId) {
-                       return _forms.has(formId);
-               },
-               
-               /**
-                * Registers the given field for the form with the given id.
-                * 
-                * @param       {string}                                        formId
-                * @param       {WoltLabSuite/Core/Form/Builder/Field/Field}    field
-                */
-               registerField: function(formId, field) {
-                       if (!this.hasForm(formId)) {
-                               throw new Error("Unknown form with id '" + formId + "'.");
-                       }
-                       
-                       if (!(field instanceof FormBuilderField)) {
-                               throw new Error("Add field is no instance of 'WoltLabSuite/Core/Form/Builder/Field/Field'.");
-                       }
-                       
-                       var fieldId = field.getId();
-                       
-                       if (this.hasField(formId, fieldId)) {
-                               throw new Error("Form field with id '" + fieldId + "' has already been registered for form with id '" + fieldId + "'.");
-                       }
-                       
-                       _fields.get(formId).set(fieldId, field);
-               },
-               
-               /**
-                * Registers the form with the given id.
-                * 
-                * @param       {string}        formId
-                */
-               registerForm: function(formId) {
-                       if (this.hasForm(formId)) {
-                               throw new Error("Form with id '" + formId + "' has already been registered.");
-                       }
-                       
-                       var form = elById(formId);
-                       if (form === null) {
-                               throw new Error("Unknown form with id '" + formId + "'.");
-                       }
-                       
-                       _forms.set(formId, form);
-                       _fields.set(formId, new Dictionary());
-               },
-               
-               /**
-                * Unregisters the form with the given id.
-                * 
-                * @param       {string}        formId
-                */
-               unregisterForm: function(formId) {
-                       if (!this.hasForm(formId)) {
-                               throw new Error("Unknown form with id '" + formId + "'.");
-                       }
-                       
-                       _forms.delete(formId);
-                       
-                       _fields.get(formId).forEach(function(field) {
-                               field.destroy();
-                       });
-                       
-                       _fields.delete(formId);
-                       
-                       FormBuilderFieldDependencyManager.unregister(formId);
-               }
-       };
-});
-
-/**
- * Provides API to easily create a dialog form created by form builder.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Dialog
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Dialog',['Ajax', 'Core', './Manager', 'Ui/Dialog'], function(Ajax, Core, FormBuilderManager, UiDialog) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function FormBuilderDialog(dialogId, className, actionName, options) {
-               this.init(dialogId, className, actionName, options);
-       };
-       FormBuilderDialog.prototype = {
-               /**
-                * Initializes the dialog.
-                * 
-                * @param       {string}        dialogId
-                * @param       {string}        className
-                * @param       {string}        actionName
-                * @param       {{actionParameters: object, destoryOnClose: boolean, dialog: object}}   options
-                */
-               init: function(dialogId, className, actionName, options) {
-                       this._dialogId = dialogId;
-                       this._className = className;
-                       this._actionName = actionName;
-                       this._options = Core.extend({
-                               actionParameters: {},
-                               destroyOnClose: false,
-                               usesDboAction: this._className.match(/\w+\\data\\/)
-                       }, options);
-                       this._options.dialog = Core.extend(this._options.dialog || {}, {
-                               onClose: this._dialogOnClose.bind(this)
-                       });
-                       
-                       this._formId = '';
-                       this._dialogContent = '';
-               },
-               
-               /**
-                * Returns the data for Ajax to setup the Ajax/Request object.
-                * 
-                * @return      {object}        setup data for Ajax/Request object
-                */
-               _ajaxSetup: function() {
-                       var options = {
-                               data: {
-                                       actionName: this._actionName,
-                                       className: this._className,
-                                       parameters: this._options.actionParameters
-                               }
-                       };
-                       
-                       // by default, `AJAXProxyAction` is used which relies on an `IDatabaseObjectAction`
-                       // object; if no such object is used but an `IAJAXInvokeAction` object,
-                       // `AJAXInvokeAction` has to be used
-                       if (!this._options.usesDboAction) {
-                               options.url = 'index.php?ajax-invoke/&t=' + SECURITY_TOKEN;
-                               options.withCredentials = true;
-                       }
-                       
-                       return options;
-               },
-               
-               /**
-                * Handles successful Ajax requests.
-                *
-                * @param       {object}        data    response data
-                */
-               _ajaxSuccess: function(data) {
-                       switch (data.actionName) {
-                               case this._actionName:
-                                       if (data.returnValues === undefined) {
-                                               throw new Error("Missing return data.");
-                                       }
-                                       else if (data.returnValues.dialog === undefined) {
-                                               throw new Error("Missing dialog template in return data.");
-                                       }
-                                       else if (data.returnValues.formId === undefined) {
-                                               throw new Error("Missing form id in return data.");
-                                       }
-                                       
-                                       this._openDialogContent(data.returnValues.formId, data.returnValues.dialog);
-                                       
-                                       break;
-                                       
-                               case this._options.submitActionName:
-                                       // if the validation failed, the dialog is shown again
-                                       if (data.returnValues && data.returnValues.formId && data.returnValues.dialog) {
-                                               if (data.returnValues.formId !== this._formId) {
-                                                       throw new Error("Mismatch between form ids: expected '" + this._formId + "' but got '" + data.returnValues.formId + "'.");
-                                               }
-                                               
-                                               this._openDialogContent(data.returnValues.formId, data.returnValues.dialog);
-                                       }
-                                       else {
-                                               this.destroy();
-                                               
-                                               if (typeof this._options.successCallback === 'function') {
-                                                       this._options.successCallback(data.returnValues || {});
-                                               }
-                                       }
-                                       
-                                       break;
-                                       
-                               default:
-                                       throw new Error("Cannot handle action '" + data.actionName + "'.");
-                       }
-               },
-               
-               /**
-                * Is called when clicking on the dialog form's close button.
-                */
-               _closeDialog: function() {
-                       UiDialog.close(this);
-               },
-               
-               /**
-                * Is called by the dialog API when the dialog is closed.
-                */
-               _dialogOnClose: function() {
-                       if (this._options.destroyOnClose) {
-                               this.destroy();
-                       }
-               },
-               
-               /**
-                * Returns the data used to setup the dialog.
-                * 
-                * @return      {object}        setup data
-                */
-               _dialogSetup: function() {
-                       return {
-                               id: this._dialogId,
-                               options : this._options.dialog,
-                               source: this._dialogContent
-                       };
-               },
-               
-               /**
-                * Is called by the dialog API when the dialog form is submitted.
-                */
-               _dialogSubmit: function() {
-                       this.getData().then(this._submitForm.bind(this));
-               },
-               
-               /**
-                * Opens the form dialog with the given form content.
-                * 
-                * @param       {string}        formId
-                * @param       {string}        dialogContent
-                */
-               _openDialogContent: function(formId, dialogContent) {
-                       this.destroy(true);
-                       
-                       this._formId = formId;
-                       this._dialogContent = dialogContent;
-                       
-                       var dialogData = UiDialog.open(this, this._dialogContent);
-                       
-                       var cancelButton = elBySel('button[data-type=cancel]', dialogData.content);
-                       if (cancelButton !== null && !elDataBool(cancelButton, 'has-event-listener')) {
-                               cancelButton.addEventListener('click', this._closeDialog.bind(this));
-                               elData(cancelButton, 'has-event-listener', 1);
-                       }
-               },
-               
-               /**
-                * Submits the form with the given form data.
-                * 
-                * @param       {object}        formData
-                */
-               _submitForm: function(formData) {
-                       var submitButton = elBySel('button[data-type=submit]',  UiDialog.getDialog(this).content);
-                       
-                       if (typeof this._options.onSubmit === 'function') {
-                               this._options.onSubmit(formData, submitButton);
-                       }
-                       else if (typeof this._options.submitActionName === 'string') {
-                               submitButton.disabled = true;
-                               
-                               Ajax.api(this, {
-                                       actionName: this._options.submitActionName,
-                                       parameters: {
-                                               data: formData,
-                                               formId: this._formId
-                                       }
-                               });
-                       }
-               },
-               
-               /**
-                * Destroys the dialog.
-                * 
-                * @param       {boolean}       ignoreDialog    if `true`, the actual dialog is not destroyed, only the form is
-                */
-               destroy: function(ignoreDialog) {
-                       if (this._formId !== '') {
-                               if (FormBuilderManager.hasForm(this._formId)) {
-                                       FormBuilderManager.unregisterForm(this._formId);
-                               }
-                               
-                               if (ignoreDialog !== true) {
-                                       UiDialog.destroy(this);
-                               }
-                       }
-               },
-               
-               /**
-                * Returns a promise that all of the dialog form's data.
-                * 
-                * @return      {Promise}
-                */
-               getData: function() {
-                       if (this._formId === '') {
-                               throw new Error("Form has not been requested yet.");
-                       }
-                       
-                       return FormBuilderManager.getData(this._formId);
-               },
-               
-               /**
-                * Opens the dialog form.
-                */
-               open: function() {
-                       if (UiDialog.getDialog(this._dialogId)) {
-                               UiDialog.openStatic(this._dialogId);
-                       }
-                       else {
-                               Ajax.api(this);
-                       }
-               }
-       };
-       
-       return FormBuilderDialog;
-});
-
-/**
- * Provides the media search for the media manager.
- *
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Media/Manager/Search
- */
-define('WoltLabSuite/Core/Media/Manager/Search',['Ajax', 'Core', 'Dom/Traverse', 'Dom/Util', 'EventKey', 'Language', 'Ui/SimpleDropdown'], function(Ajax, Core, DomTraverse, DomUtil, EventKey, Language, UiSimpleDropdown) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       _ajaxSetup: function() {},
-                       _ajaxSuccess: function() {},
-                       _cancelSearch: function() {},
-                       _keyPress: function() {},
-                       _search: function() {},
-                       hideSearch: function() {},
-                       resetSearch: function() {},
-                       showSearch: function() {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * @constructor
-        */
-       function MediaManagerSearch(mediaManager) {
-               this._mediaManager = mediaManager;
-               this._searchMode = false;
-               
-               this._searchContainer = elByClass('mediaManagerSearch', mediaManager.getDialog())[0];
-               this._input = elByClass('mediaManagerSearchField', mediaManager.getDialog())[0];
-               this._input.addEventListener('keypress', this._keyPress.bind(this));
-               
-               this._cancelButton = elByClass('mediaManagerSearchCancelButton', mediaManager.getDialog())[0];
-               this._cancelButton.addEventListener(WCF_CLICK_EVENT, this._cancelSearch.bind(this));
-       }
-       MediaManagerSearch.prototype = {
-               /**
-                * Returns the data for Ajax to setup the Ajax/Request object.
-                *
-                * @return      {object}        setup data for Ajax/Request object
-                */
-               _ajaxSetup: function() {
-                       return {
-                               data: {
-                                       actionName: 'getSearchResultList',
-                                       className: 'wcf\\data\\media\\MediaAction',
-                                       interfaceName: 'wcf\\data\\ISearchAction'
-                               }
-                       };
-               },
-               
-               /**
-                * Handles successful AJAX requests.
-                *
-                * @param       {object}        data    response data
-                */
-               _ajaxSuccess: function(data) {
-                       this._mediaManager.setMedia(data.returnValues.media || { }, data.returnValues.template || '', {
-                               pageCount: data.returnValues.pageCount || 0,
-                               pageNo: data.returnValues.pageNo || 0
-                       });
-                       
-                       elByClass('dialogContent', this._mediaManager.getDialog())[0].scrollTop = 0;
-               },
-               
-               /**
-                * Cancels the search after clicking on the cancel search button.
-                */
-               _cancelSearch: function() {
-                       if (this._searchMode) {
-                               this._searchMode = false;
-                               
-                               this.resetSearch();
-                               this._mediaManager.resetMedia();
-                       }
-               },
-               
-               /**
-                * Hides the search string threshold error.
-                */
-               _hideStringThresholdError: function() {
-                       var innerInfo = DomTraverse.childByClass(this._input.parentNode.parentNode, 'innerInfo');
-                       if (innerInfo) {
-                               elHide(innerInfo);
-                       }
-               },
-               
-               /**
-                * Handles the `[ENTER]` key to submit the form.
-                *
-                * @param       {Event}         event           event object
-                */
-               _keyPress: function(event) {
-                       if (EventKey.Enter(event)) {
-                               event.preventDefault();
-                               
-                               if (this._input.value.length >= this._mediaManager.getOption('minSearchLength')) {
-                                       this._hideStringThresholdError();
-                                       
-                                       this.search();
-                               }
-                               else {
-                                       this._showStringThresholdError();
-                               }
-                       }
-               },
-               
-               /**
-                * Shows the search string threshold error.
-                */
-               _showStringThresholdError: function() {
-                       var innerInfo = DomTraverse.childByClass(this._input.parentNode.parentNode, 'innerInfo');
-                       if (innerInfo) {
-                               elShow(innerInfo);
-                       }
-                       else {
-                               innerInfo = elCreate('p');
-                               innerInfo.className = 'innerInfo';
-                               innerInfo.textContent = Language.get('wcf.media.search.info.searchStringThreshold', {
-                                       minSearchLength: this._mediaManager.getOption('minSearchLength')
-                               });
-                               
-                               DomUtil.insertAfter(innerInfo, this._input.parentNode);
-                       }
-               },
-               
-               /**
-                * Hides the media search.
-                */
-               hideSearch: function() {
-                       elHide(this._searchContainer);
-               },
-               
-               /**
-                * Resets the media search.
-                */
-               resetSearch: function() {
-                       this._input.value = '';
-               },
-               
-               /**
-                * Shows the media search.
-                */
-               showSearch: function() {
-                       elShow(this._searchContainer);
-               },
-               
-               /**
-                * Sends an AJAX request to fetch search results.
-                * 
-                * @param       {integer}       pageNo
-                */
-               search: function(pageNo) {
-                       if (typeof pageNo !== "number") {
-                               pageNo = 1;
-                       }
-                       
-                       var searchString = this._input.value;
-                       if (searchString && this._input.value.length < this._mediaManager.getOption('minSearchLength')) {
-                               this._showStringThresholdError();
-                               
-                               searchString = '';
-                       }
-                       else {
-                               this._hideStringThresholdError();
-                       }
-                       
-                       this._searchMode = true;
-                       
-                       Ajax.api(this, {
-                               parameters: {
-                                       categoryID: this._mediaManager.getCategoryId(),
-                                       imagesOnly: this._mediaManager.getOption('imagesOnly'),
-                                       mode: this._mediaManager.getMode(),
-                                       pageNo: pageNo,
-                                       searchString: searchString
-                               }
-                       });
-               },
-       };
-       
-       return MediaManagerSearch;
-});
-
-/**
- * Provides the media manager dialog.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Media/Manager/Base
- */
-define(
-       'WoltLabSuite/Core/Media/Manager/Base',[
-               'Core',                     'Dictionary',               'Dom/ChangeListener',              'Dom/Traverse',
-               'Dom/Util',                 'EventHandler',             'Language',                        'List',
-               'Permission',               'Ui/Dialog',                'Ui/Notification',                 'WoltLabSuite/Core/Controller/Clipboard',
-               'WoltLabSuite/Core/Media/Editor', 'WoltLabSuite/Core/Media/Upload', 'WoltLabSuite/Core/Media/Manager/Search', 'StringUtil',
-               'WoltLabSuite/Core/Ui/Pagination',
-               'WoltLabSuite/Core/Media/Clipboard'
-       ],
-       function(
-               Core,                        Dictionary,                 DomChangeListener,                 DomTraverse,
-               DomUtil,                     EventHandler,               Language,                          List,
-               Permission,                  UiDialog,                   UiNotification,                    Clipboard,
-               MediaEditor,                 MediaUpload,                MediaManagerSearch,                StringUtil,
-               UiPagination,
-               MediaClipboard
-       )
-{
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       _addButtonEventListeners: function() {},
-                       _click: function() {},
-                       _dialogClose: function() {},
-                       _dialogInit: function() {},
-                       _dialogSetup: function() {},
-                       _dialogShow: function() {},
-                       _editMedia: function() {},
-                       _editorClose: function() {},
-                       _editorSuccess: function() {},
-                       _removeClipboardCheckboxes: function() {},
-                       _setMedia: function() {},
-                       addMedia: function() {},
-                       clipboardDeleteMedia: function() {},
-                       getDialog: function() {},
-                       getMode: function() {},
-                       getOption: function() {},
-                       removeMedia: function() {},
-                       resetMedia: function() {},
-                       setMedia: function() {},
-                       setupMediaElement: function() {}
-               };
-               return Fake;
-       }
-       
-       var _mediaManagerCounter = 0;
-       
-       /**
-        * @constructor
-        */
-       function MediaManagerBase(options) {
-               this._options = Core.extend({
-                       dialogTitle: Language.get('wcf.media.manager'),
-                       imagesOnly: false,
-                       minSearchLength: 3
-               }, options);
-               
-               this._id = 'mediaManager' + _mediaManagerCounter++;
-               this._listItems = new Dictionary();
-               this._media = new Dictionary();
-               this._mediaManagerMediaList = null;
-               this._search = null;
-               this._upload = null;
-               this._forceClipboard = false;
-               this._hadInitiallyMarkedItems = false;
-               this._pagination = null;
-               
-               if (Permission.get('admin.content.cms.canManageMedia')) {
-                       this._mediaEditor = new MediaEditor(this);
-               }
-               
-               DomChangeListener.add('WoltLabSuite/Core/Media/Manager', this._addButtonEventListeners.bind(this));
-               
-               EventHandler.add('com.woltlab.wcf.media.upload', 'success', this._openEditorAfterUpload.bind(this));
-       }
-       MediaManagerBase.prototype = {
-               /**
-                * Adds click event listeners to media buttons.
-                */
-               _addButtonEventListeners: function() {
-                       if (!this._mediaManagerMediaList) return;
-                       
-                       var listItems = DomTraverse.childrenByTag(this._mediaManagerMediaList, 'LI');
-                       for (var i = 0, length = listItems.length; i < length; i++) {
-                               var listItem = listItems[i];
-                               
-                               if (Permission.get('admin.content.cms.canManageMedia')) {
-                                       var editIcon = elByClass('jsMediaEditButton', listItem)[0];
-                                       if (editIcon) {
-                                               editIcon.classList.remove('jsMediaEditButton');
-                                               editIcon.addEventListener(WCF_CLICK_EVENT, this._editMedia.bind(this));
-                                       }
-                               }
-                       }
-               },
-               
-               /**
-                * Is called when a new category is selected.
-                */
-               _categoryChange: function() {
-                       this._search.search();
-               },
-               
-               /**
-                * Handles clicks on the media manager button.
-                * 
-                * @param       {object}        event   event object
-                */
-               _click: function(event) {
-                       event.preventDefault();
-                       
-                       UiDialog.open(this);
-               },
-               
-               /**
-                * Is called if the media manager dialog is closed.
-                */
-               _dialogClose: function() {
-                       // only show media clipboard if editor is open
-                       if (Permission.get('admin.content.cms.canManageMedia') || this._forceClipboard) {
-                               Clipboard.hideEditor('com.woltlab.wcf.media');
-                       }
-               },
-               
-               /**
-                * Initializes the dialog when first loaded.
-                *
-                * @param       {string}        content         dialog content
-                * @param       {object}        data            AJAX request's response data
-                */
-               _dialogInit: function(content, data) {
-                       // store media data locally
-                       var media = data.returnValues.media || { };
-                       for (var mediaId in media) {
-                               if (objOwns(media, mediaId)) {
-                                       this._media.set(~~mediaId, media[mediaId]);
-                               }
-                       }
-                       
-                       this._initPagination(~~data.returnValues.pageCount);
-                       
-                       this._hadInitiallyMarkedItems = data.returnValues.hasMarkedItems;
-               },
-               
-               /**
-                * Returns all data to setup the media manager dialog.
-                * 
-                * @return      {object}        dialog setup data
-                */
-               _dialogSetup: function() {
-                       return {
-                               id: this._id,
-                               options: {
-                                       onClose: this._dialogClose.bind(this),
-                                       onShow: this._dialogShow.bind(this),
-                                       title: this._options.dialogTitle
-                               },
-                               source: {
-                                       after: this._dialogInit.bind(this),
-                                       data: {
-                                               actionName: 'getManagementDialog',
-                                               className: 'wcf\\data\\media\\MediaAction',
-                                               parameters: {
-                                                       mode: this.getMode(),
-                                                       imagesOnly: this._options.imagesOnly
-                                               }
-                                       }
-                               }
-                       };
-               },
-               
-               /**
-                * Is called if the media manager dialog is shown.
-                */
-               _dialogShow: function() {
-                       if (!this._mediaManagerMediaList) {
-                               var dialog = this.getDialog();
-                               
-                               this._mediaManagerMediaList = elByClass('mediaManagerMediaList', dialog)[0];
-                               
-                               this._mediaCategorySelect = elBySel('.mediaManagerCategoryList > select', dialog);
-                               if (this._mediaCategorySelect) {
-                                       this._mediaCategorySelect.addEventListener('change', this._categoryChange.bind(this));
-                               }
-                               
-                               // store list items locally
-                               var listItems = DomTraverse.childrenByTag(this._mediaManagerMediaList, 'LI');
-                               for (var i = 0, length = listItems.length; i < length; i++) {
-                                       var listItem = listItems[i];
-                                       
-                                       this._listItems.set(~~elData(listItem, 'object-id'), listItem);
-                               }
-                               
-                               if (Permission.get('admin.content.cms.canManageMedia')) {
-                                       var uploadButton = elByClass('mediaManagerMediaUploadButton', UiDialog.getDialog(this).dialog)[0];
-                                       this._upload = new MediaUpload(DomUtil.identify(uploadButton), DomUtil.identify(this._mediaManagerMediaList), {
-                                               mediaManager: this
-                                       });
-                                       
-                                       var deleteAction = new WCF.Action.Delete('wcf\\data\\media\\MediaAction', '.mediaFile');
-                                       deleteAction._didTriggerEffect = function(element) {
-                                               this.removeMedia(elData(element[0], 'object-id'));
-                                       }.bind(this);
-                               }
-                               
-                               if (Permission.get('admin.content.cms.canManageMedia') || this._forceClipboard) {
-                                       MediaClipboard.init(
-                                               'menuManagerDialog-' + this.getMode(),
-                                               this._hadInitiallyMarkedItems ? true : false,
-                                               this
-                                       );
-                               }
-                               else {
-                                       this._removeClipboardCheckboxes();
-                               }
-                               
-                               this._search = new MediaManagerSearch(this);
-                               
-                               if (!listItems.length) {
-                                       this._search.hideSearch();
-                               }
-                       }
-                       
-                       // only show media clipboard if editor is open
-                       if (Permission.get('admin.content.cms.canManageMedia') || this._forceClipboard) {
-                               Clipboard.showEditor('com.woltlab.wcf.media');
-                       }
-               },
-               
-               /**
-                * Opens the media editor for a media file.
-                * 
-                * @param       {Event}         event           event object for clicks on edit icons
-                */
-               _editMedia: function(event) {
-                       if (!Permission.get('admin.content.cms.canManageMedia')) {
-                               throw new Error("You are not allowed to edit media files.");
-                       }
-                       
-                       UiDialog.close(this);
-                       
-                       this._mediaEditor.edit(this._media.get(~~elData(event.currentTarget, 'object-id')));
-               },
-               
-               /**
-                * Re-opens the manager dialog after closing the editor dialog.
-                */
-               _editorClose: function() {
-                       UiDialog.open(this);
-               },
-               
-               /**
-                * Re-opens the manager dialog and updates the media data after
-                * successfully editing a media file.
-                * 
-                * @param       {object}        media           updated media file data
-                * @param       {integer}       oldCategoryId   old category id
-                */
-               _editorSuccess: function(media, oldCategoryId) {
-                       // if the category changed of media changed and category
-                       // is selected, check if media list needs to be refreshed
-                       if (this._mediaCategorySelect) {
-                               var selectedCategoryId = ~~this._mediaCategorySelect.value;
-                               
-                               if (selectedCategoryId) {
-                                       var newCategoryId = ~~media.categoryID;
-                                       
-                                       if (oldCategoryId != newCategoryId && (oldCategoryId == selectedCategoryId || newCategoryId == selectedCategoryId)) {
-                                               this._search.search();
-                                       }
-                               }
-                       }
-                       
-                       UiDialog.open(this);
-                       
-                       this._media.set(~~media.mediaID, media);
-                       
-                       var listItem = this._listItems.get(~~media.mediaID);
-                       var p = elByClass('mediaTitle', listItem)[0];
-                       if (media.isMultilingual) {
-                               p.textContent = media.title[LANGUAGE_ID] || media.filename;
-                       }
-                       else {
-                               p.textContent = media.title[media.languageID] || media.filename;
-                       }
-               },
-               
-               /**
-                * Initializes the dialog pagination.
-                *
-                * @param       {integer}       pageCount
-                * @param       {integer}       pageNo
-                */
-               _initPagination: function(pageCount, pageNo) {
-                       if (pageNo === undefined) pageNo = 1;
-                       
-                       if (pageCount > 1) {
-                               var newPagination = elCreate('div');
-                               newPagination.className = 'paginationBottom jsPagination';
-                               DomUtil.replaceElement(elBySel('.jsPagination', UiDialog.getDialog(this).content), newPagination);
-                               
-                               this._pagination = new UiPagination(newPagination, {
-                                       activePage: pageNo,
-                                       callbackSwitch: this._search.search.bind(this._search),
-                                       maxPage: pageCount
-                               });
-                       }
-                       else if (this._pagination) {
-                               elHide(this._pagination.getElement());
-                       }
-               },
-               
-               /**
-                * Removes all media clipboard checkboxes.
-                */
-               _removeClipboardCheckboxes: function() {
-                       var checkboxes = elByClass('mediaCheckbox', this._mediaManagerMediaList);
-                       while (checkboxes.length) {
-                               elRemove(checkboxes[0]);
-                       }
-               },
-               
-               /**
-                * Opens the media editor after uploading a single file.
-                * 
-                * @param       {object}        data    upload event data
-                * @since       5.2
-                */
-               _openEditorAfterUpload: function(data) {
-                       if (data.upload === this._upload && !data.isMultiFileUpload && !this._upload.hasPendingUploads()) {
-                               var keys = Object.keys(data.media);
-                               
-                               if (keys.length) {
-                                       UiDialog.close(this);
-                                       
-                                       this._mediaEditor.edit(this._media.get(~~data.media[keys[0]].mediaID));
-                               }
-                       }
-               },
-               
-               /**
-                * Sets the displayed media (after a search).
-                * 
-                * @param       {Dictionary}    media           media to be set as active
-                */
-               _setMedia: function(media) {
-                       if (Core.isPlainObject(media)) {
-                               this._media = Dictionary.fromObject(media);
-                       }
-                       else {
-                               this._media = media;
-                       }
-                       
-                       var info = DomTraverse.nextByClass(this._mediaManagerMediaList, 'info');
-                       
-                       if (this._media.size) {
-                               if (info) {
-                                       elHide(info);
-                               }
-                       }
-                       else {
-                               if (info === null) {
-                                       info = elCreate('p');
-                                       info.className = 'info';
-                                       info.textContent = Language.get('wcf.media.search.noResults');
-                               }
-                               
-                               elShow(info);
-                               DomUtil.insertAfter(info, this._mediaManagerMediaList);
-                       }
-                       
-                       var mediaListItems = DomTraverse.childrenByTag(this._mediaManagerMediaList, 'LI');
-                       for (var i = 0, length = mediaListItems.length; i < length; i++) {
-                               var listItem = mediaListItems[i];
-                               
-                               if (!this._media.has(elData(listItem, 'object-id'))) {
-                                       elHide(listItem);
-                               }
-                               else {
-                                       elShow(listItem);
-                               }
-                       }
-                       
-                       DomChangeListener.trigger();
-                       
-                       if (Permission.get('admin.content.cms.canManageMedia') || this._forceClipboard) {
-                               Clipboard.reload();
-                       }
-                       else {
-                               this._removeClipboardCheckboxes();
-                       }
-               },
-               
-               /**
-                * Adds a media file to the manager.
-                * 
-                * @param       {object}        media           data of the media file
-                * @param       {Element}       listItem        list item representing the file
-                */
-               addMedia: function(media, listItem) {
-                       if (!media.languageID) media.isMultilingual = 1;
-                       
-                       this._media.set(~~media.mediaID, media);
-                       this._listItems.set(~~media.mediaID, listItem);
-                       
-                       if (this._listItems.size === 1) {
-                               this._search.showSearch();
-                       }
-               },
-               
-               /**
-                * Is called after the media files with the given ids have been deleted via clipboard.
-                * 
-                * @param       {int[]}         mediaIds        ids of deleted media files
-                */
-               clipboardDeleteMedia: function(mediaIds) {
-                       for (var i = 0, length = mediaIds.length; i < length; i++) {
-                               this.removeMedia(~~mediaIds[i], true);
-                       }
-                       
-                       UiNotification.show();
-               },
-               
-               /**
-                * Returns the id of the currently selected category or `0` if no category is selected.
-                * 
-                * @return      {integer}
-                */
-               getCategoryId: function() {
-                       if (this._mediaCategorySelect) {
-                               return this._mediaCategorySelect.value;
-                       }
-                       
-                       return 0;
-               },
-               
-               /**
-                * Returns the media manager dialog element.
-                * 
-                * @return      {Element}       media manager dialog
-                */
-               getDialog: function() {
-                       return UiDialog.getDialog(this).dialog;
-               },
-               
-               /**
-                * Returns the mode of the media manager.
-                *
-                * @return      {string}
-                */
-               getMode: function() {
-                       return '';
-               },
-               
-               /**
-                * Returns the media manager option with the given name.
-                * 
-                * @param       {string}        name            option name
-                * @return      {mixed}         option value or null
-                */
-               getOption: function(name) {
-                       if (this._options[name]) {
-                               return this._options[name];
-                       }
-                       
-                       return null;
-               },
-               
-               /**
-                * Removes a media file.
-                *
-                * @param       {int}                   mediaId         id of the removed media file
-                */
-               removeMedia: function(mediaId) {
-                       if (this._listItems.has(mediaId)) {
-                               // remove list item
-                               try {
-                                       elRemove(this._listItems.get(mediaId));
-                               }
-                               catch (e) {
-                                       // ignore errors if item has already been removed like by WCF.Action.Delete
-                               }
-                               
-                               this._listItems.delete(mediaId);
-                               this._media.delete(mediaId);
-                       }
-               },
-               
-               /**
-                * Changes the displayed media to the previously displayed media.
-                */
-               resetMedia: function() {
-                       // calling WoltLabSuite/Core/Media/Manager/Search.search() reloads the first page of the dialog
-                       this._search.search();
-               },
-               
-               /**
-                * Sets the media files currently displayed.
-                * 
-                * @param       {object}        media           media data
-                * @param       {string}        template        
-                * @param       {object}        additionalData
-                */
-               setMedia: function(media, template, additionalData) {
-                       var hasMedia = false;
-                       for (var mediaId in media) {
-                               if (objOwns(media, mediaId)) {
-                                       hasMedia = true;
-                               }
-                       }
-                       
-                       var newListItems = [];
-                       if (hasMedia) {
-                               var ul = elCreate('ul');
-                               ul.innerHTML = template;
-                               
-                               var listItems = DomTraverse.childrenByTag(ul, 'LI');
-                               for (var i = 0, length = listItems.length; i < length; i++) {
-                                       var listItem = listItems[i];
-                                       if (!this._listItems.has(~~elData(listItem, 'object-id'))) {
-                                               this._listItems.set(elData(listItem, 'object-id'), listItem);
-                                               
-                                               this._mediaManagerMediaList.appendChild(listItem);
-                                       }
-                               }
-                       }
-                       
-                       this._initPagination(additionalData.pageCount, additionalData.pageNo);
-                       
-                       this._setMedia(media);
-               },
-               
-               /**
-                * Sets up a new media element.
-                * 
-                * @param       {object}        media           data of the media file
-                * @param       {HTMLElement}   mediaElement    element representing the media file
-                */
-               setupMediaElement: function(media, mediaElement) {
-                       var mediaInformation = DomTraverse.childByClass(mediaElement, 'mediaInformation');
-                       
-                       var buttonGroupNavigation = elCreate('nav');
-                       buttonGroupNavigation.className = 'jsMobileNavigation buttonGroupNavigation';
-                       mediaInformation.parentNode.appendChild(buttonGroupNavigation);
-                       
-                       var buttons = elCreate('ul');
-                       buttons.className = 'buttonList iconList';
-                       buttonGroupNavigation.appendChild(buttons);
-                       
-                       var listItem = elCreate('li');
-                       listItem.className = 'mediaCheckbox';
-                       buttons.appendChild(listItem);
-                       
-                       var a = elCreate('a');
-                       listItem.appendChild(a);
-                       
-                       var label = elCreate('label');
-                       a.appendChild(label);
-                       
-                       var checkbox = elCreate('input');
-                       checkbox.className = 'jsClipboardItem';
-                       elAttr(checkbox, 'type', 'checkbox');
-                       elData(checkbox, 'object-id', media.mediaID);
-                       label.appendChild(checkbox);
-                       
-                       if (Permission.get('admin.content.cms.canManageMedia')) {
-                               listItem = elCreate('li');
-                               listItem.className = 'jsMediaEditButton';
-                               elData(listItem, 'object-id', media.mediaID);
-                               buttons.appendChild(listItem);
-                               
-                               listItem.innerHTML = '<a><span class="icon icon16 fa-pencil jsTooltip" title="' + Language.get('wcf.global.button.edit') + '"></span> <span class="invisible">' + Language.get('wcf.global.button.edit') + '</span></a>';
-                               
-                               listItem = elCreate('li');
-                               listItem.className = 'jsDeleteButton';
-                               elData(listItem, 'object-id', media.mediaID);
-                               
-                               // use temporary title to not unescape html in filename
-                               var uuid = Core.getUuid();
-                               elData(listItem, 'confirm-message-html', StringUtil.unescapeHTML(Language.get('wcf.media.delete.confirmMessage', {
-                                       title: uuid
-                               })).replace(uuid, StringUtil.escapeHTML(media.filename)));
-                               buttons.appendChild(listItem);
-                               
-                               listItem.innerHTML = '<a><span class="icon icon16 fa-times jsTooltip" title="' + Language.get('wcf.global.button.delete') + '"></span> <span class="invisible">' + Language.get('wcf.global.button.delete') + '</span></a>';
-                       }
-               }
-       };
-       
-       return MediaManagerBase;
-});
-
-/**
- * Provides the media manager dialog for selecting media for Redactor editors.
- *
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Media/Manager/Editor
- */
-define('WoltLabSuite/Core/Media/Manager/Editor',['Core', 'Dictionary', 'Dom/Traverse', 'EventHandler', 'Language', 'Permission', 'Ui/Dialog', 'WoltLabSuite/Core/Controller/Clipboard', 'WoltLabSuite/Core/Media/Manager/Base'],
-       function(Core, Dictionary, DomTraverse, EventHandler, Language, Permission, UiDialog, ControllerClipboard, MediaManagerBase) {
-       "use strict";
-               
-               if (!COMPILER_TARGET_DEFAULT) {
-                       var Fake = function() {};
-                       Fake.prototype = {
-                               _addButtonEventListeners: function() {},
-                               _buildInsertDialog: function() {},
-                               _click: function() {},
-                               _getInsertDialogId: function() {},
-                               _getThumbnailSizes: function() {},
-                               _insertMedia: function() {},
-                               _insertMediaGallery: function() {},
-                               _insertMediaItem: function() {},
-                               _openInsertDialog: function() {},
-                               insertMedia: function() {},
-                               getMode: function() {},
-                               setupMediaElement: function() {},
-                               _dialogClose: function() {},
-                               _dialogInit: function() {},
-                               _dialogSetup: function() {},
-                               _dialogShow: function() {},
-                               _editMedia: function() {},
-                               _editorClose: function() {},
-                               _editorSuccess: function() {},
-                               _removeClipboardCheckboxes: function() {},
-                               _setMedia: function() {},
-                               addMedia: function() {},
-                               clipboardInsertMedia: function() {},
-                               getDialog: function() {},
-                               getOption: function() {},
-                               removeMedia: function() {},
-                               resetMedia: function() {},
-                               setMedia: function() {}
-                       };
-                       return Fake;
-               }
-       
-       /**
-        * @constructor
-        */
-       function MediaManagerEditor(options) {
-               options = Core.extend({
-                       callbackInsert: null
-               }, options);
-               
-               MediaManagerBase.call(this, options);
-               
-               this._forceClipboard = true;
-               this._activeButton = null;
-               var context = (this._options.editor) ? this._options.editor.core.toolbar()[0] : undefined;
-               this._buttons = elByClass(this._options.buttonClass || 'jsMediaEditorButton', context);
-               for (var i = 0, length = this._buttons.length; i < length; i++) {
-                       this._buttons[i].addEventListener(WCF_CLICK_EVENT, this._click.bind(this));
-               }
-               this._mediaToInsert = new Dictionary();
-               this._mediaToInsertByClipboard = false;
-               this._uploadData = null;
-               this._uploadId = null;
-               
-               if (this._options.editor && !this._options.editor.opts.woltlab.attachments) {
-                       var editorId = elData(this._options.editor.$editor[0], 'element-id');
-                       
-                       var uuid1 = EventHandler.add('com.woltlab.wcf.redactor2', 'dragAndDrop_' + editorId, this._editorUpload.bind(this));
-                       var uuid2 = EventHandler.add('com.woltlab.wcf.redactor2', 'pasteFromClipboard_' + editorId, this._editorUpload.bind(this));
-                       
-                       EventHandler.add('com.woltlab.wcf.redactor2', 'destory_' + editorId, function() {
-                               EventHandler.remove('com.woltlab.wcf.redactor2', 'dragAndDrop_' + editorId, uuid1);
-                               EventHandler.remove('com.woltlab.wcf.redactor2', 'dragAndDrop_' + editorId, uuid2);
-                       });
-                       
-                       EventHandler.add('com.woltlab.wcf.media.upload', 'success', this._mediaUploaded.bind(this));
-               }
-       }
-       Core.inherit(MediaManagerEditor, MediaManagerBase, {
-               /**
-                * @see WoltLabSuite/Core/Media/Manager/Base#_addButtonEventListeners
-                */
-               _addButtonEventListeners: function() {
-                       MediaManagerEditor._super.prototype._addButtonEventListeners.call(this);
-                       
-                       if (!this._mediaManagerMediaList) return;
-                       
-                       var listItems = DomTraverse.childrenByTag(this._mediaManagerMediaList, 'LI');
-                       for (var i = 0, length = listItems.length; i < length; i++) {
-                               var listItem = listItems[i];
-                               
-                               var insertIcon = elByClass('jsMediaInsertButton', listItem)[0];
-                               if (insertIcon) {
-                                       insertIcon.classList.remove('jsMediaInsertButton');
-                                       insertIcon.addEventListener(WCF_CLICK_EVENT, this._openInsertDialog.bind(this));
-                               }
-                       }
-               },
-               
-               /**
-                * Builds the dialog to setup inserting media files.
-                */
-               _buildInsertDialog: function() {
-                       var thumbnailOptions = '';
-                       
-                       var thumbnailSizes = this._getThumbnailSizes();
-                       for (var i = 0, length = thumbnailSizes.length; i < length; i++) {
-                               thumbnailOptions += '<option value="' + thumbnailSizes[i] + '">' + Language.get('wcf.media.insert.imageSize.' + thumbnailSizes[i]) + '</option>';
-                       }
-                       thumbnailOptions += '<option value="original">' + Language.get('wcf.media.insert.imageSize.original') + '</option>';
-                       
-                       var dialog = '<div class="section">'
-                       /*+ (this._mediaToInsert.size > 1 ? '<dl>'
-                               + '<dt>' + Language.get('wcf.media.insert.type') + '</dt>'
-                               + '<dd>'
-                                       + '<select name="insertType">'
-                                               + '<option value="separate">' + Language.get('wcf.media.insert.type.separate') + '</option>'
-                                               + '<option value="gallery">' + Language.get('wcf.media.insert.type.gallery') + '</option>'
-                                       + '</select>'
-                               + '</dd>'
-                       + '</dl>' : '')*/
-                       + '<dl class="thumbnailSizeSelection">'
-                               + '<dt>' + Language.get('wcf.media.insert.imageSize') + '</dt>'
-                               + '<dd>'
-                                       + '<select name="thumbnailSize">'
-                                               + thumbnailOptions
-                                       + '</select>'
-                               + '</dd>'
-                       + '</dl>'
-                       + '</div>'
-                       + '<div class="formSubmit">'
-                               + '<button class="buttonPrimary">' + Language.get('wcf.global.button.insert') + '</button>'
-                       + '</div>';
-                       
-                       UiDialog.open({
-                               _dialogSetup: (function() {
-                                       return {
-                                               id: this._getInsertDialogId(),
-                                               options: {
-                                                       onClose: this._editorClose.bind(this),
-                                                       onSetup: function(content) {
-                                                               elByClass('buttonPrimary', content)[0].addEventListener(WCF_CLICK_EVENT, this._insertMedia.bind(this));
-                                                               
-                                                               // toggle thumbnail size selection based on selected insert type
-                                                               /*var insertType = elBySel('select[name=insertType]', content);
-                                                               if (insertType !== null) {
-                                                                       var thumbnailSelection = elByClass('thumbnailSizeSelection', content)[0];
-                                                                       insertType.addEventListener('change', function(event) {
-                                                                               if (event.currentTarget.value === 'gallery') {
-                                                                                       elHide(thumbnailSelection);
-                                                                               }
-                                                                               else {
-                                                                                       elShow(thumbnailSelection);
-                                                                               }
-                                                                       });
-                                                               }*/
-                                                               var thumbnailSelection = elBySel('.thumbnailSizeSelection', content);
-                                                               elShow(thumbnailSelection);
-                                                       }.bind(this),
-                                                       title: Language.get('wcf.media.insert')
-                                               },
-                                               source: dialog
-                                       };
-                               }).bind(this)
-                       });
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Media/Manager/Base#_click
-                */
-               _click: function(event) {
-                       this._activeButton = event.currentTarget;
-                       
-                       MediaManagerEditor._super.prototype._click.call(this, event);
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Media/Manager/Base#_dialogShow
-                */
-               _dialogShow: function() {
-                       MediaManagerEditor._super.prototype._dialogShow.call(this);
-                       
-                       // check if data needs to be uploaded
-                       if (this._uploadData) {
-                               if (this._uploadData.file) {
-                                       this._upload.uploadFile(this._uploadData.file);
-                               }
-                               else {
-                                       this._uploadId = this._upload.uploadBlob(this._uploadData.blob);
-                               }
-                               
-                               this._uploadData = null;
-                       }
-               },
-               
-               /**
-                * Handles pasting and dragging and dropping files into the editor. 
-                * 
-                * @param       {object}        data    data of the uploaded file
-                */
-               _editorUpload: function(data) {
-                       this._uploadData = data;
-                       
-                       UiDialog.open(this);
-               },
-               
-               /**
-                * Returns the id of the insert dialog based on the media files to be inserted.
-                * 
-                * @return      {string}        insert dialog id
-                */
-               _getInsertDialogId: function() {
-                       var dialogId = 'mediaInsert';
-                       
-                       this._mediaToInsert.forEach(function(media, mediaId) {
-                               dialogId += '-' + mediaId;
-                       });
-                       
-                       return dialogId;
-               },
-               
-               /**
-                * Returns the supported thumbnail sizes (excluding `original`) for all media images to be inserted.
-                * 
-                * @return      {string[]}
-                */
-               _getThumbnailSizes: function() {
-                       var sizes = [];
-                       
-                       var supportedSizes = ['small', 'medium', 'large'];
-                       var size, supportSize;
-                       for (var i = 0, length = supportedSizes.length; i < length; i++) {
-                               size = supportedSizes[i];
-                               
-                               supportSize = true;
-                               this._mediaToInsert.forEach(function(media) {
-                                       if (!media[size + 'ThumbnailType']) {
-                                               supportSize = false;
-                                       }
-                               });
-                               
-                               if (supportSize) {
-                                       sizes.push(size);
-                               }
-                       }
-                       
-                       return sizes;
-               },
-               
-               /**
-                * Inserts media files into redactor.
-                * 
-                * @param       {Event?}        event
-                * @param       {string?}       thumbnailSize
-                * @param       {boolean?}      closeEditor
-                */
-               _insertMedia: function(event, thumbnailSize, closeEditor) {
-                       if (closeEditor === undefined) closeEditor = true;
-                       
-                       var insertType = 'separate';
-                       
-                       // update insert options with selected values if method is called by clicking on 'insert' button
-                       // in dialog
-                       if (event) {
-                               UiDialog.close(this._getInsertDialogId());
-                               
-                               var dialogContent = event.currentTarget.closest('.dialogContent');
-                               
-                               /*if (this._mediaToInsert.size > 1) {
-                                       insertType = elBySel('select[name=insertType]', dialogContent).value;
-                               }*/
-                               thumbnailSize = elBySel('select[name=thumbnailSize]', dialogContent).value;
-                       }
-                       
-                       if (this._options.callbackInsert !== null) {
-                               this._options.callbackInsert(this._mediaToInsert, insertType, thumbnailSize);
-                       }
-                       else {
-                               if (insertType === 'separate') {
-                                       this._options.editor.buffer.set();
-                                       
-                                       this._mediaToInsert.forEach(this._insertMediaItem.bind(this, thumbnailSize));
-                               }
-                               else {
-                                       this._insertMediaGallery();
-                               }
-                       }
-                       
-                       if (this._mediaToInsertByClipboard) {
-                               var mediaIds = [];
-                               this._mediaToInsert.forEach(function(media) {
-                                       mediaIds.push(media.mediaID);
-                               });
-                               
-                               ControllerClipboard.unmark('com.woltlab.wcf.media', mediaIds);
-                       }
-                       
-                       this._mediaToInsert = new Dictionary();
-                       this._mediaToInsertByClipboard = false;
-                       
-                       // close manager dialog
-                       if (closeEditor) {
-                               UiDialog.close(this);
-                       }
-               },
-               
-               /**
-                * Inserts a series of uploaded images using a slider.
-                * 
-                * @protected
-                */
-               _insertMediaGallery: function() {
-                       var mediaIds = [];
-                       this._mediaToInsert.forEach(function(item) {
-                               mediaIds.push(item.mediaID);
-                       });
-                       
-                       this._options.editor.buffer.set();
-                       this._options.editor.insert.text("[wsmg='" + mediaIds.join(',') + "'][/wsmg]");
-               },
-               
-               /**
-                * Inserts a single media item.
-                * 
-                * @param       {string}        thumbnailSize   preferred image dimension, is ignored for non-images
-                * @param       {Object}        item            media item data
-                * @protected
-                */
-               _insertMediaItem: function(thumbnailSize, item) {
-                       if (item.isImage) {
-                               var sizes = ['small', 'medium', 'large', 'original'];
-                               
-                               // check if size is actually available
-                               var available = '', size;
-                               for (var i = 0; i < 4; i++) {
-                                       size = sizes[i];
-                                       
-                                       if (item[size + 'ThumbnailHeight'] != 0) {
-                                               available = size;
-                                               
-                                               if (thumbnailSize == size) {
-                                                       break;
-                                               }
-                                       }
-                               }
-                               
-                               thumbnailSize = available;
-                               
-                               if (!thumbnailSize) thumbnailSize = 'original';
-                               
-                               var link = item.link;
-                               if (thumbnailSize !== 'original') {
-                                       link = item[thumbnailSize + 'ThumbnailLink'];
-                               }
-                               
-                               this._options.editor.insert.html('<img src="' + link + '" class="woltlabSuiteMedia" data-media-id="' + item.mediaID + '" data-media-size="' + thumbnailSize + '">');
-                       }
-                       else {
-                               this._options.editor.insert.text("[wsm='" + item.mediaID + "'][/wsm]");
-                       }
-               },
-               
-               /**
-                * Is called after media files are successfully uploaded to insert copied media.
-                * 
-                * @param       {object}        data            upload data
-                */
-               _mediaUploaded: function(data) {
-                       if (this._uploadId !== null && this._upload === data.upload) {
-                               if (this._uploadId === data.uploadId || (Array.isArray(this._uploadId) && this._uploadId.indexOf(data.uploadId) !== -1)) {
-                                       this._mediaToInsert = Dictionary.fromObject(data.media);
-                                       this._insertMedia(null, 'medium', false);
-                                       
-                                       this._uploadId = null;
-                               }
-                       }
-               },
-               
-               /**
-                * Handles clicking on the insert button.
-                * 
-                * @param       {Event}         event           insert button click event
-                */
-               _openInsertDialog: function(event) {
-                       this.insertMedia([~~elData(event.currentTarget, 'object-id')]);
-               },
-               
-               /**
-                * Is called to insert the media files with the given ids into an editor.
-                * 
-                * @param       {int[]}         mediaIds
-                */
-               clipboardInsertMedia: function(mediaIds) {
-                       this.insertMedia(mediaIds, true);
-               },
-               
-               /**
-                * Prepares insertion of the media files with the given ids.
-                * 
-                * @param       {array<int>}    mediaIds                ids of the media files to be inserted
-                * @param       {boolean?}      insertedByClipboard     is true if the media files are inserted by clipboard
-                */
-               insertMedia: function(mediaIds, insertedByClipboard) {
-                       this._mediaToInsert = new Dictionary();
-                       this._mediaToInsertByClipboard = insertedByClipboard || false;
-                       
-                       // open the insert dialog if all media files are images
-                       var imagesOnly = true, media;
-                       for (var i = 0, length = mediaIds.length; i < length; i++) {
-                               media = this._media.get(mediaIds[i]);
-                               this._mediaToInsert.set(media.mediaID, media);
-                               
-                               if (!media.isImage) {
-                                       imagesOnly = false;
-                               }
-                       }
-                       
-                       if (imagesOnly) {
-                               var thumbnailSizes = this._getThumbnailSizes();
-                               if (thumbnailSizes.length) {
-                                       UiDialog.close(this);
-                                       var dialogId = this._getInsertDialogId();
-                                       if (UiDialog.getDialog(dialogId)) {
-                                               UiDialog.openStatic(dialogId);
-                                       }
-                                       else {
-                                               this._buildInsertDialog();
-                                       }
-                               }
-                               else {
-                                       this._insertMedia(undefined, 'original');
-                               }
-                       }
-                       else {
-                               this._insertMedia();
-                       }
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Media/Manager/Base#getMode
-                */
-               getMode: function() {
-                       return 'editor';
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Media/Manager/Base#setupMediaElement
-                */
-               setupMediaElement: function(media, mediaElement) {
-                       MediaManagerEditor._super.prototype.setupMediaElement.call(this, media, mediaElement);
-                       
-                       // add media insertion icon
-                       var buttons = elBySel('nav.buttonGroupNavigation > ul', mediaElement);
-                       
-                       var listItem = elCreate('li');
-                       listItem.className = 'jsMediaInsertButton';
-                       elData(listItem, 'object-id', media.mediaID);
-                       buttons.appendChild(listItem);
-                       
-                       listItem.innerHTML = '<a><span class="icon icon16 fa-plus jsTooltip" title="' + Language.get('wcf.media.button.insert') + '"></span> <span class="invisible">' + Language.get('wcf.media.button.insert') + '</span></a>';
-               }
-       });
-       
-       return MediaManagerEditor;
-});
-
-/**
- * Provides the media manager dialog for selecting media for input elements.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Media/Manager/Select
- */
-define('WoltLabSuite/Core/Media/Manager/Select',['Core', 'Dom/Traverse', 'Dom/Util', 'Language', 'ObjectMap', 'Ui/Dialog', 'WoltLabSuite/Core/FileUtil', 'WoltLabSuite/Core/Media/Manager/Base'],
-       function(Core, DomTraverse, DomUtil, Language, ObjectMap, UiDialog, FileUtil, MediaManagerBase) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       _addButtonEventListeners: function() {},
-                       _chooseMedia: function() {},
-                       _click: function() {},
-                       getMode: function() {},
-                       setupMediaElement: function() {},
-                       _removeMedia: function() {},
-                       _clipboardAction: function() {},
-                       _dialogClose: function() {},
-                       _dialogInit: function() {},
-                       _dialogSetup: function() {},
-                       _dialogShow: function() {},
-                       _editMedia: function() {},
-                       _editorClose: function() {},
-                       _editorSuccess: function() {},
-                       _removeClipboardCheckboxes: function() {},
-                       _setMedia: function() {},
-                       addMedia: function() {},
-                       getDialog: function() {},
-                       getOption: function() {},
-                       removeMedia: function() {},
-                       resetMedia: function() {},
-                       setMedia: function() {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * @constructor
-        */
-       function MediaManagerSelect(options) {
-               MediaManagerBase.call(this, options);
-               
-               this._activeButton = null;
-               this._buttons = elByClass(this._options.buttonClass || 'jsMediaSelectButton');
-               this._storeElements = new ObjectMap();
-               
-               for (var i = 0, length = this._buttons.length; i < length; i++) {
-                       var button = this._buttons[i];
-                       
-                       // only consider buttons with a proper store specified
-                       var store = elData(button, 'store');
-                       if (store) {
-                               var storeElement = elById(store);
-                               if (storeElement && storeElement.tagName === 'INPUT') {
-                                       this._buttons[i].addEventListener(WCF_CLICK_EVENT, this._click.bind(this));
-                                       
-                                       this._storeElements.set(button, storeElement);
-                                       
-                                       // add remove button
-                                       var removeButton = elCreate('p');
-                                       removeButton.className = 'button';
-                                       DomUtil.insertAfter(removeButton, button);
-                                       
-                                       var icon = elCreate('span');
-                                       icon.className = 'icon icon16 fa-times';
-                                       removeButton.appendChild(icon);
-                                       
-                                       if (!storeElement.value) elHide(removeButton);
-                                       removeButton.addEventListener(WCF_CLICK_EVENT, this._removeMedia.bind(this));
-                               }
-                       }
-               }
-       }
-       Core.inherit(MediaManagerSelect, MediaManagerBase, {
-               /**
-                * @see WoltLabSuite/Core/Media/Manager/Base#_addButtonEventListeners
-                */
-               _addButtonEventListeners: function() {
-                       MediaManagerSelect._super.prototype._addButtonEventListeners.call(this);
-                       
-                       if (!this._mediaManagerMediaList) return;
-                       
-                       var listItems = DomTraverse.childrenByTag(this._mediaManagerMediaList, 'LI');
-                       for (var i = 0, length = listItems.length; i < length; i++) {
-                               var listItem = listItems[i];
-                               
-                               var chooseIcon = elByClass('jsMediaSelectButton', listItem)[0];
-                               if (chooseIcon) {
-                                       chooseIcon.classList.remove('jsMediaSelectButton');
-                                       chooseIcon.addEventListener(WCF_CLICK_EVENT, this._chooseMedia.bind(this));
-                               }
-                       }
-               },
-               
-               /**
-                * Handles clicking on a media choose icon.
-                * 
-                * @param       {Event}         event           click event
-                */
-               _chooseMedia: function(event) {
-                       if (this._activeButton === null) {
-                               throw new Error("Media cannot be chosen if no button is active.");
-                       }
-                       
-                       var media = this._media.get(~~elData(event.currentTarget, 'object-id'));
-                       
-                       // save selected media in store element
-                       elById(elData(this._activeButton, 'store')).value = media.mediaID;
-                       
-                       // display selected media
-                       var display = elData(this._activeButton, 'display');
-                       if (display) {
-                               var displayElement = elById(display);
-                               if (displayElement) {
-                                       if (media.isImage) {
-                                               displayElement.innerHTML = '<img src="' + (media.smallThumbnailLink ? media.smallThumbnailLink : media.link) + '" alt="' + (media.altText && media.altText[LANGUAGE_ID] ? media.altText[LANGUAGE_ID] : '') + '" />';
-                                       }
-                                       else {
-                                               var fileIcon = FileUtil.getIconNameByFilename(media.filename);
-                                               if (fileIcon) {
-                                                       fileIcon = '-' + fileIcon;
-                                               }
-                                               
-                                               displayElement.innerHTML = '<div class="box48" style="margin-bottom: 10px;">'
-                                                       + '<span class="icon icon48 fa-file' + fileIcon + '-o"></span>'
-                                                       + '<div class="containerHeadline">'
-                                                               + '<h3>' + media.filename + '</h3>'
-                                                               + '<p>' + media.formattedFilesize + '</p>'
-                                                       + '</div>'
-                                               + '</div>';
-                                       }
-                               }
-                       }
-                       
-                       // show remove button
-                       elShow(this._activeButton.nextElementSibling);
-                       
-                       UiDialog.close(this);
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Media/Manager/Base#_click
-                */
-               _click: function(event) {
-                       event.preventDefault();
-                       this._activeButton = event.currentTarget;
-                       
-                       MediaManagerSelect._super.prototype._click.call(this, event);
-                       
-                       if (!this._mediaManagerMediaList) return;
-                       
-                       var storeElement = this._storeElements.get(this._activeButton);
-                       var listItems = DomTraverse.childrenByTag(this._mediaManagerMediaList, 'LI'), listItem;
-                       for (var i = 0, length = listItems.length; i < length; i++) {
-                               listItem = listItems[i];
-                               if (storeElement.value && storeElement.value == elData(listItem, 'object-id')) {
-                                       listItem.classList.add('jsSelected');
-                               }
-                               else {
-                                       listItem.classList.remove('jsSelected');
-                               }
-                       }
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Media/Manager/Base#getMode
-                */
-               getMode: function() {
-                       return 'select';
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Media/Manager/Base#setupMediaElement
-                */
-               setupMediaElement: function(media, mediaElement) {
-                       MediaManagerSelect._super.prototype.setupMediaElement.call(this, media, mediaElement);
-                       
-                       // add media insertion icon
-                       var buttons = elBySel('nav.buttonGroupNavigation > ul', mediaElement);
-                       
-                       var listItem = elCreate('li');
-                       listItem.className = 'jsMediaSelectButton';
-                       elData(listItem, 'object-id', media.mediaID);
-                       buttons.appendChild(listItem);
-                       
-                       listItem.innerHTML = '<a><span class="icon icon16 fa-check jsTooltip" title="' + Language.get('wcf.media.button.select') + '"></span> <span class="invisible">' + Language.get('wcf.media.button.select') + '</span></a>';
-               },
-               
-               /**
-                * Handles clicking on the remove button.
-                *
-                * @param       {Event}         event           click event
-                */
-               _removeMedia: function(event) {
-                       event.preventDefault();
-                       
-                       var removeButton = event.currentTarget;
-                       elHide(removeButton);
-                       
-                       var button = removeButton.previousElementSibling;
-                       elById(elData(button, 'store')).value = 0;
-                       var display = elData(button, 'display');
-                       if (display) {
-                               var displayElement = elById(display);
-                               if (displayElement) {
-                                       displayElement.innerHTML = '';
-                               }
-                       }
-               }
-       });
-       
-       return MediaManagerSelect;
-});
-
-/**
- * Provides suggestions using an input field, designed to work with `wcf\data\ISearchAction`.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Search/Input
- */
-define('WoltLabSuite/Core/Ui/Search/Input',['Ajax', 'Core', 'EventKey', 'Dom/Util', 'Ui/SimpleDropdown'], function(Ajax, Core, EventKey, DomUtil, UiSimpleDropdown) {
-       "use strict";
-       
-       /**
-        * @param       {Element}       element         target input[type="text"]
-        * @param       {Object}        options         search options and settings
-        * @constructor
-        */
-       function UiSearchInput(element, options) { this.init(element, options); }
-       UiSearchInput.prototype = {
-               /**
-                * Initializes the search input field.
-                * 
-                * @param       {Element}       element         target input[type="text"]
-                * @param       {Object}        options         search options and settings
-                */
-               init: function(element, options) {
-                       this._element = element;
-                       if (!(this._element instanceof Element)) {
-                               throw new TypeError("Expected a valid DOM element.");
-                       }
-                       else if (this._element.nodeName !== 'INPUT' || (this._element.type !== 'search' && this._element.type !== 'text')) {
-                               throw new Error('Expected an input[type="text"].');
-                       }
-                       
-                       this._activeItem = null;
-                       this._dropdownContainerId = '';
-                       this._lastValue = '';
-                       this._list = null;
-                       this._request = null;
-                       this._timerDelay = null;
-                       
-                       this._options = Core.extend({
-                               ajax: {
-                                       actionName: 'getSearchResultList',
-                                       className: '',
-                                       interfaceName: 'wcf\\data\\ISearchAction'
-                               },
-                               callbackDropdownInit: null,
-                               callbackSelect: null,
-                               delay: 500,
-                               excludedSearchValues: [],
-                               minLength: 3,
-                               noResultPlaceholder: '',
-                               preventSubmit: false
-                       }, options);
-                       
-                       // disable auto-complete as it collides with the suggestion dropdown
-                       elAttr(this._element, 'autocomplete', 'off');
-                       
-                       this._element.addEventListener('keydown', this._keydown.bind(this));
-                       this._element.addEventListener('keyup', this._keyup.bind(this));
-               },
-               
-               /**
-                * Adds an excluded search value.
-                * 
-                * @param       {string}        value   excluded value
-                */
-               addExcludedSearchValues: function (value) {
-                       if (this._options.excludedSearchValues.indexOf(value) === -1) {
-                               this._options.excludedSearchValues.push(value);
-                       }
-               },
-               
-               /**
-                * Removes a value from the excluded search values.
-                * 
-                * @param       {string}        value   excluded value
-                */
-               removeExcludedSearchValues: function (value) {
-                       var index = this._options.excludedSearchValues.indexOf(value);
-                       if (index !== -1) {
-                               this._options.excludedSearchValues.splice(index, 1);
-                       }
-               },
-               
-               /**
-                * Handles the 'keydown' event.
-                * 
-                * @param       {Event}         event           event object
-                * @protected
-                */
-               _keydown: function(event) {
-                       if ((this._activeItem !== null && UiSimpleDropdown.isOpen(this._dropdownContainerId)) || this._options.preventSubmit) {
-                               if (EventKey.Enter(event)) {
-                                       event.preventDefault();
-                               }
-                       }
-                       
-                       if (EventKey.ArrowUp(event) || EventKey.ArrowDown(event) || EventKey.Escape(event)) {
-                               event.preventDefault();
-                       }
-               },
-               
-               /**
-                * Handles the 'keyup' event, provides keyboard navigation and executes search queries.
-                * 
-                * @param       {Event}         event           event object
-                * @protected
-                */
-               _keyup: function(event) {
-                       // handle dropdown keyboard navigation
-                       if (this._activeItem !== null) {
-                               if (UiSimpleDropdown.isOpen(this._dropdownContainerId)) {
-                                       if (EventKey.ArrowUp(event)) {
-                                               event.preventDefault();
-                                               
-                                               return this._keyboardPreviousItem();
-                                       }
-                                       else if (EventKey.ArrowDown(event)) {
-                                               event.preventDefault();
-                                               
-                                               return this._keyboardNextItem();
-                                       }
-                                       else if (EventKey.Enter(event)) {
-                                               event.preventDefault();
-                                               
-                                               return this._keyboardSelectItem();
-                                       }
-                               }
-                               else {
-                                       this._activeItem = null;
-                               }
-                       }
-                       
-                       // close list on escape
-                       if (EventKey.Escape(event)) {
-                               UiSimpleDropdown.close(this._dropdownContainerId);
-                               
-                               return;
-                       }
-                       
-                       var value = this._element.value.trim();
-                       if (this._lastValue === value) {
-                               // value did not change, e.g. previously it was "Test" and now it is "Test ",
-                               // but the trailing whitespace has been ignored
-                               return;
-                       }
-                       
-                       this._lastValue = value;
-                       
-                       if (value.length < this._options.minLength) {
-                               if (this._dropdownContainerId) {
-                                       UiSimpleDropdown.close(this._dropdownContainerId);
-                                       this._activeItem = null;
-                               }
-                               
-                               // value below threshold
-                               return;
-                       }
-                       
-                       if (this._options.delay) {
-                               if (this._timerDelay !== null) {
-                                       window.clearTimeout(this._timerDelay);
-                               }
-                               
-                               this._timerDelay = window.setTimeout((function() {
-                                       this._search(value);
-                               }).bind(this), this._options.delay);
-                       }
-                       else {
-                               this._search(value);
-                       }
-               },
-               
-               /**
-                * Queries the server with the provided search string.
-                * 
-                * @param       {string}        value   search string
-                * @protected
-                */
-               _search: function(value) {
-                       if (this._request) {
-                               this._request.abortPrevious();
-                       }
-                       
-                       this._request = Ajax.api(this, this._getParameters(value));
-               },
-               
-               /**
-                * Returns additional AJAX parameters.
-                * 
-                * @param       {string}        value   search string
-                * @return      {Object}        additional AJAX parameters
-                * @protected
-                */
-               _getParameters: function(value) {
-                       return {
-                               parameters: {
-                                       data: {
-                                               excludedSearchValues: this._options.excludedSearchValues,
-                                               searchString: value
-                                       }
-                               }
-                       };
-               },
-               
-               /**
-                * Selects the next dropdown item.
-                * 
-                * @protected
-                */
-               _keyboardNextItem: function() {
-                       this._activeItem.classList.remove('active');
-                       
-                       if (this._activeItem.nextElementSibling) {
-                               this._activeItem = this._activeItem.nextElementSibling;
-                       }
-                       else {
-                               this._activeItem = this._list.children[0];
-                       }
-                       
-                       this._activeItem.classList.add('active');
-               },
-               
-               /**
-                * Selects the previous dropdown item.
-                * 
-                * @protected
-                */
-               _keyboardPreviousItem: function() {
-                       this._activeItem.classList.remove('active');
-                       
-                       if (this._activeItem.previousElementSibling) {
-                               this._activeItem = this._activeItem.previousElementSibling;
-                       }
-                       else {
-                               this._activeItem = this._list.children[this._list.childElementCount - 1];
-                       }
-                       
-                       this._activeItem.classList.add('active');
-               },
-               
-               /**
-                * Selects the active item from the dropdown.
-                * 
-                * @protected
-                */
-               _keyboardSelectItem: function() {
-                       this._selectItem(this._activeItem);
-               },
-               
-               /**
-                * Selects an item from the dropdown by clicking it.
-                * 
-                * @param       {Event}         event           event object
-                * @protected
-                */
-               _clickSelectItem: function(event) {
-                       this._selectItem(event.currentTarget);
-               },
-               
-               /**
-                * Selects an item.
-                * 
-                * @param       {Element}       item    selected item
-                * @protected
-                */
-               _selectItem: function(item) {
-                       if (this._options.callbackSelect && this._options.callbackSelect(item) === false) {
-                               this._element.value = '';
-                       }
-                       else {
-                               this._element.value = elData(item, 'label');
-                       }
-                       
-                       this._activeItem = null;
-                       UiSimpleDropdown.close(this._dropdownContainerId);
-               },
-               
-               /**
-                * Handles successful AJAX requests.
-                * 
-                * @param       {Object}        data    response data
-                * @protected
-                */
-               _ajaxSuccess: function(data) {
-                       var createdList = false;
-                       if (this._list === null) {
-                               this._list = elCreate('ul');
-                               this._list.className = 'dropdownMenu';
-                               
-                               createdList = true;
-                               
-                               if (typeof this._options.callbackDropdownInit === 'function') {
-                                       this._options.callbackDropdownInit(this._list);
-                               }
-                       }
-                       else {
-                               // reset current list
-                               this._list.innerHTML = '';
-                       }
-                       
-                       if (typeof data.returnValues === 'object') {
-                               var callbackClick = this._clickSelectItem.bind(this), listItem;
-                               
-                               for (var key in data.returnValues) {
-                                       if (data.returnValues.hasOwnProperty(key)) {
-                                               listItem = this._createListItem(data.returnValues[key]);
-                                               
-                                               listItem.addEventListener(WCF_CLICK_EVENT, callbackClick);
-                                               this._list.appendChild(listItem);
-                                       }
-                               }
-                       }
-                       
-                       if (createdList) {
-                               DomUtil.insertAfter(this._list, this._element);
-                               UiSimpleDropdown.initFragment(this._element.parentNode, this._list);
-                               
-                               this._dropdownContainerId = DomUtil.identify(this._element.parentNode);
-                       }
-                       
-                       if (this._dropdownContainerId) {
-                               this._activeItem = null;
-                               
-                               if (!this._list.childElementCount && this._handleEmptyResult() === false) {
-                                       UiSimpleDropdown.close(this._dropdownContainerId);
-                               }
-                               else {
-                                       UiSimpleDropdown.open(this._dropdownContainerId, true);
-                                       
-                                       // mark first item as active
-                                       if (this._list.childElementCount && ~~elData(this._list.children[0], 'object-id')) {
-                                               this._activeItem = this._list.children[0];
-                                               this._activeItem.classList.add('active');
-                                       }
-                               }
-                       }
-               },
-               
-               /**
-                * Handles an empty result set, return a boolean false to hide the dropdown.
-                * 
-                * @return      {boolean}      false to close the dropdown
-                * @protected
-                */
-               _handleEmptyResult: function() {
-                       if (!this._options.noResultPlaceholder) {
-                               return false;
-                       }
-                       
-                       var listItem = elCreate('li');
-                       listItem.className = 'dropdownText';
-                       
-                       var span = elCreate('span');
-                       span.textContent = this._options.noResultPlaceholder;
-                       listItem.appendChild(span);
-                       
-                       this._list.appendChild(listItem);
-                       
-                       return true;
-               },
-               
-               /**
-                * Creates an list item from response data.
-                * 
-                * @param       {Object}        item    response data
-                * @return      {Element}       list item
-                * @protected
-                */
-               _createListItem: function(item) {
-                       var listItem = elCreate('li');
-                       elData(listItem, 'object-id', item.objectID);
-                       elData(listItem, 'label', item.label);
-                       
-                       var span = elCreate('span');
-                       span.textContent = item.label;
-                       listItem.appendChild(span);
-                       
-                       return listItem;
-               },
-               
-               _ajaxSetup: function() {
-                       return {
-                               data: this._options.ajax
-                       };
-               }
-       };
-       
-       return UiSearchInput;
-});
-
-/**
- * Provides suggestions for users, optionally supporting groups.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/User/Search/Input
- * @see         module:WoltLabSuite/Core/Ui/Search/Input
- */
-define('WoltLabSuite/Core/Ui/User/Search/Input',['Core', 'WoltLabSuite/Core/Ui/Search/Input'], function(Core, UiSearchInput) {
-       "use strict";
-       
-       /**
-        * @param       {Element}       element         input element
-        * @param       {Object=}       options         search options and settings
-        * @constructor
-        */
-       function UiUserSearchInput(element, options) { this.init(element, options); }
-       Core.inherit(UiUserSearchInput, UiSearchInput, {
-               init: function(element, options) {
-                       var includeUserGroups = (Core.isPlainObject(options) && options.includeUserGroups === true);
-                       
-                       options = Core.extend({
-                               ajax: {
-                                       className: 'wcf\\data\\user\\UserAction',
-                                       parameters: {
-                                               data: {
-                                                       includeUserGroups: (includeUserGroups ? 1 : 0)
-                                               }
-                                       }
-                               }
-                       }, options);
-                       
-                       UiUserSearchInput._super.prototype.init.call(this, element, options);
-               },
-               
-               _createListItem: function(item) {
-                       var listItem = UiUserSearchInput._super.prototype._createListItem.call(this, item);
-                       elData(listItem, 'type', item.type);
-                       
-                       var box = elCreate('div');
-                       box.className = 'box16';
-                       box.innerHTML = (item.type === 'group') ? '<span class="icon icon16 fa-users"></span>' : item.icon;
-                       box.appendChild(listItem.children[0]);
-                       listItem.appendChild(box);
-                       
-                       return listItem;
-               }
-       });
-       
-       return UiUserSearchInput;
-});
-
-define('WoltLabSuite/Core/Ui/Acl/Simple',['Language', 'StringUtil', 'Dom/ChangeListener', 'WoltLabSuite/Core/Ui/User/Search/Input'], function(Language, StringUtil, DomChangeListener, UiUserSearchInput) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _build: function() {},
-                       _select: function() {},
-                       _removeItem: function() {}
-               };
-               return Fake;
-       }
-       
-       function UiAclSimple(prefix, inputName) { this.init(prefix, inputName); }
-       UiAclSimple.prototype = {
-               init: function(prefix, inputName) {
-                       this._prefix = prefix || '';
-                       this._inputName = inputName || 'aclValues';
-                       
-                       this._build();
-               },
-               
-               _build: function () {
-                       var container = elById(this._prefix + 'aclInputContainer');
-                       
-                       elById(this._prefix + 'aclAllowAll').addEventListener('change', (function() {
-                               elHide(container);
-                       }));
-                       elById(this._prefix + 'aclAllowAll_no').addEventListener('change', (function() {
-                               elShow(container);
-                       }));
-                       
-                       this._list = elById(this._prefix + 'aclAccessList');
-                       this._list.addEventListener(WCF_CLICK_EVENT, this._removeItem.bind(this));
-                       
-                       var excludedSearchValues = [];
-                       elBySelAll('.aclLabel', this._list, function(label) {
-                               excludedSearchValues.push(label.textContent);
-                       });
-                       
-                       this._searchInput = new UiUserSearchInput(elById(this._prefix + 'aclSearchInput'), {
-                               callbackSelect: this._select.bind(this),
-                               includeUserGroups: true,
-                               excludedSearchValues: excludedSearchValues,
-                               preventSubmit: true,
-                       });
-                       
-                       this._aclListContainer = elById(this._prefix + 'aclListContainer');
-                       
-                       DomChangeListener.trigger();
-               },
-               
-               _select: function(listItem) {
-                       var type = elData(listItem, 'type');
-                       var label = elData(listItem, 'label');
-                       
-                       var html = '<span class="icon icon16 fa-' + (type === 'group' ? 'users' : 'user') + '"></span>';
-                       html += '<span class="aclLabel">' + StringUtil.escapeHTML(label) + '</span>';
-                       html += '<span class="icon icon16 fa-times pointer jsTooltip" title="' + Language.get('wcf.global.button.delete') + '"></span>';
-                       html += '<input type="hidden" name="' + this._inputName + '[' + type + '][]" value="' + elData(listItem, 'object-id') + '">';
-                       
-                       var item = elCreate('li');
-                       item.innerHTML = html;
-                       
-                       var firstUser = elBySel('.fa-user', this._list);
-                       if (firstUser === null) {
-                               this._list.appendChild(item);
-                       }
-                       else {
-                               this._list.insertBefore(item, firstUser.parentNode);
-                       }
-                       
-                       elShow(this._aclListContainer);
-                       
-                       this._searchInput.addExcludedSearchValues(label);
-                       
-                       DomChangeListener.trigger();
-                       
-                       return false;
-               },
-               
-               _removeItem: function (event) {
-                       if (event.target.classList.contains('fa-times')) {
-                               var label = elBySel('.aclLabel', event.target.parentNode);
-                               this._searchInput.removeExcludedSearchValues(label.textContent);
-                               
-                               elRemove(event.target.parentNode);
-                               
-                               if (this._list.childElementCount === 0) {
-                                       elHide(this._aclListContainer);
-                               }
-                       }
-               }
-       };
-       
-       return UiAclSimple;
-});
-
-/**
- * Handles the 'mark as read' action for articles.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Article/MarkAllAsRead
- */
-define('WoltLabSuite/Core/Ui/Article/MarkAllAsRead',['Ajax'], function(Ajax) {
-       "use strict";
-       
-       return {
-               init: function() {
-                       elBySelAll('.markAllAsReadButton', undefined, (function(button) {
-                               button.addEventListener(WCF_CLICK_EVENT, this._click.bind(this));
-                       }).bind(this));
-               },
-               
-               _click: function(event) {
-                       event.preventDefault();
-                       
-                       Ajax.api(this);
-               },
-               
-               _ajaxSuccess: function() {
-                       /* remove obsolete badges */
-                       // main menu
-                       var badge = elBySel('.mainMenu .active .badge');
-                       if (badge) elRemove(badge);
-                       
-                       // article list
-                       elBySelAll('.articleList .newMessageBadge', undefined, elRemove);
-               },
-               
-               _ajaxSetup: function() {
-                       return {
-                               data: {
-                                       actionName: 'markAllAsRead',
-                                       className: 'wcf\\data\\article\\ArticleAction'
-                               }
-                       };
-               }
-       };
-});
-
-define('WoltLabSuite/Core/Ui/Article/Search',['Ajax', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog'], function(Ajax, EventKey, Language, StringUtil, DomUtil, UiDialog) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       open: function() {},
-                       _search: function() {},
-                       _click: function() {},
-                       _ajaxSuccess: function() {},
-                       _ajaxSetup: function() {},
-                       _dialogSetup: function() {}
-               };
-               return Fake;
-       }
-       
-       var _callbackSelect, _resultContainer, _resultList, _searchInput = null;
-       
-       return {
-               open: function(callbackSelect) {
-                       _callbackSelect = callbackSelect;
-                       
-                       UiDialog.open(this);
-               },
-               
-               _search: function (event) {
-                       event.preventDefault();
-                       
-                       var inputContainer = _searchInput.parentNode;
-                       
-                       var value = _searchInput.value.trim();
-                       if (value.length < 3) {
-                               elInnerError(inputContainer, Language.get('wcf.article.search.error.tooShort'));
-                               return;
-                       }
-                       else {
-                               elInnerError(inputContainer, false);
-                       }
-                       
-                       Ajax.api(this, {
-                               parameters: {
-                                       searchString: value
-                               }
-                       });
-               },
-               
-               _click: function (event) {
-                       event.preventDefault();
-                       
-                       _callbackSelect(elData(event.currentTarget, 'article-id'));
-                       
-                       UiDialog.close(this);
-               },
-               
-               _ajaxSuccess: function(data) {
-                       var html = '', article;
-                       //noinspection JSUnresolvedVariable
-                       for (var i = 0, length = data.returnValues.length; i < length; i++) {
-                               //noinspection JSUnresolvedVariable
-                               article = data.returnValues[i];
-                               
-                               html += '<li>'
-                                               + '<div class="containerHeadline pointer" data-article-id="' + article.articleID + '">'
-                                                       + '<h3>' + StringUtil.escapeHTML(article.name) + '</h3>'
-                                                       + '<small>' + StringUtil.escapeHTML(article.displayLink) + '</small>'
-                                               + '</div>'
-                                       + '</li>';
-                       }
-                       
-                       _resultList.innerHTML = html;
-                       
-                       window[html ? 'elShow' : 'elHide'](_resultContainer);
-                       
-                       if (html) {
-                               elBySelAll('.containerHeadline', _resultList, (function(item) {
-                                       item.addEventListener(WCF_CLICK_EVENT, this._click.bind(this));
-                               }).bind(this));
-                       }
-                       else {
-                               elInnerError(_searchInput.parentNode, Language.get('wcf.article.search.error.noResults'));
-                       }
-               },
-               
-               _ajaxSetup: function () {
-                       return {
-                               data: {
-                                       actionName: 'search',
-                                       className: 'wcf\\data\\article\\ArticleAction'
-                               }
-                       };
-               },
-               
-               _dialogSetup: function() {
-                       return {
-                               id: 'wcfUiArticleSearch',
-                               options: {
-                                       onSetup: (function() {
-                                               var callbackSearch = this._search.bind(this);
-                                               
-                                               _searchInput = elById('wcfUiArticleSearchInput');
-                                               _searchInput.addEventListener('keydown', function(event) {
-                                                       if (EventKey.Enter(event)) {
-                                                               callbackSearch(event);
-                                                       }
-                                               });
-                                               
-                                               _searchInput.nextElementSibling.addEventListener(WCF_CLICK_EVENT, callbackSearch);
-                                               
-                                               _resultContainer = elById('wcfUiArticleSearchResultContainer');
-                                               _resultList = elById('wcfUiArticleSearchResultList');
-                                       }).bind(this),
-                                       onShow: function() {
-                                               _searchInput.focus();
-                                       },
-                                       title: Language.get('wcf.article.search')
-                               },
-                               source: '<div class="section">'
-                                       + '<dl>'
-                                               + '<dt><label for="wcfUiArticleSearchInput">' + Language.get('wcf.article.search.name') + '</label></dt>'
-                                               + '<dd>'
-                                                       + '<div class="inputAddon">'
-                                                               + '<input type="text" id="wcfUiArticleSearchInput" class="long">'
-                                                               + '<a href="#" class="inputSuffix"><span class="icon icon16 fa-search"></span></a>'
-                                                       + '</div>'
-                                               + '</dd>'
-                                       + '</dl>'
-                               + '</div>'
-                               + '<section id="wcfUiArticleSearchResultContainer" class="section" style="display: none;">'
-                                       + '<header class="sectionHeader">'
-                                               + '<h2 class="sectionTitle">' + Language.get('wcf.article.search.results') + '</h2>'
-                                       + '</header>'
-                                       + '<ol id="wcfUiArticleSearchResultList" class="containerList"></ol>'
-                               + '</section>'
-                       };
-               }
-       };
-});
-
-/**
- * Wrapper class to provide color picker support. Constructing a new object does not
- * guarantee the picker to be ready at the time of call.
- * 
- * @author      Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module      WoltLabSuite/Core/Ui/Color/Picker
- */
-define('WoltLabSuite/Core/Ui/Color/Picker',['Core'], function (Core) {
-       "use strict";
-       
-       var _marshal = function (element, options) {
-               if (typeof window.WCF === 'object' && typeof window.WCF.ColorPicker === 'function') {
-                       _marshal = function (element, options) {
-                               var picker = new window.WCF.ColorPicker(element);
-                               
-                               if (typeof options.callbackSubmit === 'function') {
-                                       picker.setCallbackSubmit(options.callbackSubmit);
-                               }
-                               
-                               return picker;
-                       };
-                       
-                       return _marshal(element, options);
-               }
-               else {
-                       if (_queue.length === 0) {
-                               window.__wcf_bc_colorPickerInit = function () {
-                                       _queue.forEach(function (data) {
-                                               _marshal(data[0], data[1]);
-                                       });
-                                       
-                                       window.__wcf_bc_colorPickerInit = undefined;
-                                       _queue = [];
-                               };
-                       }
-                       
-                       _queue.push([element, options]);
-               }
-       };
-       var _queue = [];
-       
-       /**
-        * @constructor
-        */
-       function UiColorPicker(element, options) { this.init(element, options); }
-       UiColorPicker.prototype = {
-               /**
-                * Initializes a new color picker instance. This is actually just a wrapper that does
-                * not guarantee the picker to be ready at the time of call.
-                * 
-                * @param       {Element}       element         input element
-                * @param       {Object}        options         list of initialization options
-                */
-               init: function (element, options) {
-                       if (!(element instanceof Element)) {
-                               throw new TypeError("Expected a valid DOM element, use `UiColorPicker.fromSelector()` if you want to use a CSS selector.");
-                       }
-                       
-                       this._options = Core.extend({
-                               callbackSubmit: null
-                       }, options);
-                       
-                       _marshal(element, options);
-               }
-       };
-       
-       /**
-        * Initializes a color picker for all input elements matching the given selector.
-        * 
-        * @param       {string}        selector        CSS selector
-        */
-       UiColorPicker.fromSelector = function (selector) {
-               elBySelAll(selector, undefined, function (element) {
-                       new UiColorPicker(element);
-               });
-       };
-       
-       return UiColorPicker;
-});
-
-/**
- * Handles the comment add feature.
- * 
- * Warning: This implementation is also used for responses, but in a slightly
- *          modified version. Changes made to this class need to be verified
- *          against the response implementation.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Comment/Add
- */
-define('WoltLabSuite/Core/Ui/Comment/Add',[
-       'Ajax', 'Core', 'EventHandler', 'Language', 'Dom/ChangeListener', 'Dom/Util', 'Dom/Traverse', 'Ui/Dialog', 'Ui/Notification', 'WoltLabSuite/Core/Ui/Scroll', 'EventKey', 'User', 'WoltLabSuite/Core/Controller/Captcha'
-],
-function(
-       Ajax, Core, EventHandler, Language, DomChangeListener, DomUtil, DomTraverse, UiDialog, UiNotification, UiScroll, EventKey, User, ControllerCaptcha
-) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _submitGuestDialog: function() {},
-                       _submit: function() {},
-                       _getParameters: function () {},
-                       _validate: function() {},
-                       throwError: function() {},
-                       _showLoadingOverlay: function() {},
-                       _hideLoadingOverlay: function() {},
-                       _reset: function() {},
-                       _handleError: function() {},
-                       _getEditor: function() {},
-                       _insertMessage: function() {},
-                       _ajaxSuccess: function() {},
-                       _ajaxFailure: function() {},
-                       _ajaxSetup: function() {},
-                       _cancelGuestDialog: function() {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * @constructor
-        */
-       function UiCommentAdd(container) { this.init(container); }
-       UiCommentAdd.prototype = {
-               /**
-                * Initializes a new quick reply field.
-                * 
-                * @param       {Element}       container       container element
-                */
-               init: function(container) {
-                       this._container = container;
-                       this._content = elBySel('.jsOuterEditorContainer', this._container);
-                       this._textarea = elBySel('.wysiwygTextarea', this._container);
-                       this._editor = null;
-                       this._loadingOverlay = null;
-                       
-                       this._content.addEventListener(WCF_CLICK_EVENT, (function (event) {
-                               if (this._content.classList.contains('collapsed')) {
-                                       event.preventDefault();
-                                       
-                                       this._content.classList.remove('collapsed');
-                                       
-                                       this._focusEditor();
-                               }
-                       }).bind(this));
-                       
-                       // handle submit button
-                       var submitButton = elBySel('button[data-type="save"]', this._container);
-                       submitButton.addEventListener(WCF_CLICK_EVENT, this._submit.bind(this));
-               },
-               
-               /**
-                * Scrolls the editor into view and sets the caret to the end of the editor.
-                * 
-                * @protected
-                */
-               _focusEditor: function () {
-                       UiScroll.element(this._container, (function () {
-                               window.jQuery(this._textarea).redactor('WoltLabCaret.endOfEditor');
-                       }).bind(this));
-               },
-               
-               /**
-                * Submits the guest dialog.
-                * 
-                * @param       {Event}         event
-                * @protected
-                */
-               _submitGuestDialog: function(event) {
-                       // only submit when enter key is pressed
-                       if (event.type === 'keypress' && !EventKey.Enter(event)) {
-                               return;
-                       }
-                       
-                       var usernameInput = elBySel('input[name=username]', event.currentTarget.closest('.dialogContent'));
-                       if (usernameInput.value === '') {
-                               elInnerError(usernameInput, Language.get('wcf.global.form.error.empty'));
-                               usernameInput.closest('dl').classList.add('formError');
-                               
-                               return;
-                       }
-                       
-                       var parameters = {
-                               parameters: {
-                                       data: {
-                                               username: usernameInput.value
-                                       }
-                               }
-                       };
-                       
-                       if (ControllerCaptcha.has('commentAdd')) {
-                               var data = ControllerCaptcha.getData('commentAdd');
-                               if (data instanceof Promise) {
-                                       data.then((function (data) {
-                                               parameters = Core.extend(parameters, data);
-                                               this._submit(undefined, parameters);
-                                       }).bind(this));
-                               }
-                               else {
-                                       parameters = Core.extend(parameters, data);
-                                       this._submit(undefined, parameters);
-                               }
-                       }
-                       else {
-                               this._submit(undefined, parameters);
-                       }
-               },
-               
-               /**
-                * Validates the message and submits it to the server.
-                * 
-                * @param       {Event?}        event                   event object
-                * @param       {Object?}       additionalParameters    additional parameters sent to the server
-                * @protected
-                */
-               _submit: function(event, additionalParameters) {
-                       if (event) {
-                               event.preventDefault();
-                       }
-                       
-                       if (!this._validate()) {
-                               // validation failed, bail out
-                               return;
-                       }
-                       
-                       this._showLoadingOverlay();
-                       
-                       // build parameters
-                       var parameters = this._getParameters();
-                       
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'submit_text', parameters.data);
-                       
-                       if (!User.userId && !additionalParameters) {
-                               parameters.requireGuestDialog = true;
-                       }
-                       
-                       Ajax.api(this, Core.extend({
-                               parameters: parameters
-                       }, additionalParameters));
-               },
-               
-               /**
-                * Returns the request parameters to add a comment.
-                * 
-                * @return      {{data: {message: string, objectID: number, objectTypeID: number}}}
-                * @protected
-                */
-               _getParameters: function () {
-                       var commentList = this._container.closest('.commentList');
-                       
-                       return {
-                               data: {
-                                       message: this._getEditor().code.get(),
-                                       objectID: ~~elData(commentList, 'object-id'),
-                                       objectTypeID: ~~elData(commentList, 'object-type-id')
-                               }
-                       };
-               },
-               
-               /**
-                * Validates the message and invokes listeners to perform additional validation.
-                * 
-                * @return      {boolean}       validation result
-                * @protected
-                */
-               _validate: function() {
-                       // remove all existing error elements
-                       elBySelAll('.innerError', this._container, elRemove);
-                       
-                       // check if editor contains actual content
-                       if (this._getEditor().utils.isEmpty()) {
-                               this.throwError(this._textarea, Language.get('wcf.global.form.error.empty'));
-                               return false;
-                       }
-                       
-                       var data = {
-                               api: this,
-                               editor: this._getEditor(),
-                               message: this._getEditor().code.get(),
-                               valid: true
-                       };
-                       
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'validate_text', data);
-                       
-                       return (data.valid !== false);
-               },
-               
-               /**
-                * Throws an error by adding an inline error to target element.
-                * 
-                * @param       {Element}       element         erroneous element
-                * @param       {string}        message         error message
-                */
-               throwError: function(element, message) {
-                       elInnerError(element, (message === 'empty' ? Language.get('wcf.global.form.error.empty') : message));
-               },
-               
-               /**
-                * Displays a loading spinner while the request is processed by the server.
-                * 
-                * @protected
-                */
-               _showLoadingOverlay: function() {
-                       if (this._loadingOverlay === null) {
-                               this._loadingOverlay = elCreate('div');
-                               this._loadingOverlay.className = 'commentLoadingOverlay';
-                               this._loadingOverlay.innerHTML = '<span class="icon icon96 fa-spinner"></span>';
-                       }
-                       
-                       this._content.classList.add('loading');
-                       this._content.appendChild(this._loadingOverlay);
-               },
-               
-               /**
-                * Hides the loading spinner.
-                * 
-                * @protected
-                */
-               _hideLoadingOverlay: function() {
-                       this._content.classList.remove('loading');
-                       
-                       var loadingOverlay = elBySel('.commentLoadingOverlay', this._content);
-                       if (loadingOverlay !== null) {
-                               loadingOverlay.parentNode.removeChild(loadingOverlay);
-                       }
-               },
-               
-               /**
-                * Resets the editor contents and notifies event listeners.
-                * 
-                * @protected
-                */
-               _reset: function() {
-                       this._getEditor().code.set('<p>\u200b</p>');
-                       
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'reset_text');
-                       
-                       if (document.activeElement) {
-                               document.activeElement.blur();
-                       }
-                       
-                       this._content.classList.add('collapsed');
-               },
-               
-               /**
-                * Handles errors occurred during server processing.
-                * 
-                * @param       {Object}        data    response data
-                * @protected
-                */
-               _handleError: function(data) {
-                       //noinspection JSUnresolvedVariable
-                       this.throwError(this._textarea, data.returnValues.errorType);
-               },
-               
-               /**
-                * Returns the current editor instance.
-                * 
-                * @return      {Object}       editor instance
-                * @protected
-                */
-               _getEditor: function() {
-                       if (this._editor === null) {
-                               if (typeof window.jQuery === 'function') {
-                                       this._editor = window.jQuery(this._textarea).data('redactor');
-                               }
-                               else {
-                                       throw new Error("Unable to access editor, jQuery has not been loaded yet.");
-                               }
-                       }
-                       
-                       return this._editor;
-               },
-               
-               /**
-                * Inserts the rendered message.
-                * 
-                * @param       {Object}        data    response data
-                * @return      {Element}       scroll target
-                * @protected
-                */
-               _insertMessage: function(data) {
-                       // insert HTML
-                       //noinspection JSCheckFunctionSignatures
-                       DomUtil.insertHtml(data.returnValues.template, this._container, 'after');
-                       
-                       UiNotification.show(Language.get('wcf.global.success.add'));
-                       
-                       DomChangeListener.trigger();
-                       
-                       return this._container.nextElementSibling;
-               },
-               
-               /**
-                * @param {{returnValues:{guestDialog:string}}} data
-                * @protected
-                */
-               _ajaxSuccess: function(data) {
-                       if (!User.userId && data.returnValues.guestDialog) {
-                               UiDialog.openStatic('jsDialogGuestComment', data.returnValues.guestDialog, {
-                                       closable: false,
-                                       onClose: function() {
-                                               if (ControllerCaptcha.has('commentAdd')) {
-                                                       ControllerCaptcha.delete('commentAdd');
-                                               }
-                                       },
-                                       title: Language.get('wcf.global.confirmation.title')
-                               });
-                               
-                               var dialog = UiDialog.getDialog('jsDialogGuestComment');
-                               elBySel('input[type=submit]', dialog.content).addEventListener(WCF_CLICK_EVENT, this._submitGuestDialog.bind(this));
-                               elBySel('button[data-type="cancel"]', dialog.content).addEventListener(WCF_CLICK_EVENT, this._cancelGuestDialog.bind(this));
-                               elBySel('input[type=text]', dialog.content).addEventListener('keypress', this._submitGuestDialog.bind(this));
-                       }
-                       else {
-                               var scrollTarget = this._insertMessage(data);
-                               
-                               if (!User.userId) {
-                                       UiDialog.close('jsDialogGuestComment');
-                               }
-                               
-                               this._reset();
-                               
-                               this._hideLoadingOverlay();
-                               
-                               window.setTimeout((function () {
-                                       UiScroll.element(scrollTarget);
-                               }).bind(this), 100);
-                       }
-               },
-               
-               _ajaxFailure: function(data) {
-                       this._hideLoadingOverlay();
-                       
-                       //noinspection JSUnresolvedVariable
-                       if (data === null || data.returnValues === undefined || data.returnValues.errorType === undefined) {
-                               return true;
-                       }
-                       
-                       this._handleError(data);
-                       
-                       return false;
-               },
-               
-               _ajaxSetup: function() {
-                       return {
-                               data: {
-                                       actionName: 'addComment',
-                                       className: 'wcf\\data\\comment\\CommentAction'
-                               },
-                               silent: true
-                       };
-               },
-               
-               /**
-                * Cancels the guest dialog and restores the comment editor.
-                */
-               _cancelGuestDialog: function() {
-                       UiDialog.close('jsDialogGuestComment');
-                       
-                       this._hideLoadingOverlay();
-               }
-       };
-       
-       return UiCommentAdd;
-});
-
-/**
- * Provides editing support for comments.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Comment/Edit
- */
-define(
-       'WoltLabSuite/Core/Ui/Comment/Edit',[
-               'Ajax',         'Core',            'Dictionary',          'Environment',
-               'EventHandler', 'Language',        'List',                'Dom/ChangeListener', 'Dom/Traverse',
-               'Dom/Util',     'Ui/Notification', 'Ui/ReusableDropdown', 'WoltLabSuite/Core/Ui/Scroll'
-       ],
-       function(
-               Ajax,            Core,              Dictionary,            Environment,
-               EventHandler,    Language,          List,                  DomChangeListener,    DomTraverse,
-               DomUtil,         UiNotification,    UiReusableDropdown,    UiScroll
-       )
-{
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       rebuild: function() {},
-                       _click: function() {},
-                       _prepare: function() {},
-                       _showEditor: function() {},
-                       _restoreMessage: function() {},
-                       _save: function() {},
-                       _validate: function() {},
-                       throwError: function() {},
-                       _showMessage: function() {},
-                       _hideEditor: function() {},
-                       _restoreEditor: function() {},
-                       _destroyEditor: function() {},
-                       _getEditorId: function() {},
-                       _getObjectId: function() {},
-                       _ajaxFailure: function() {},
-                       _ajaxSuccess: function() {},
-                       _ajaxSetup: function() {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * @constructor
-        */
-       function UiCommentEdit(container) { this.init(container); }
-       UiCommentEdit.prototype = {
-               /**
-                * Initializes the comment edit manager.
-                * 
-                * @param       {Element}       container       container element
-                */
-               init: function(container) {
-                       this._activeElement = null;
-                       this._callbackClick = null;
-                       this._comments = new List();
-                       this._container = container;
-                       this._editorContainer = null;
-                       
-                       this.rebuild();
-                       
-                       DomChangeListener.add('Ui/Comment/Edit_' + DomUtil.identify(this._container), this.rebuild.bind(this));
-               },
-               
-               /**
-                * Initializes each applicable message, should be called whenever new
-                * messages are being displayed.
-                */
-               rebuild: function() {
-                       elBySelAll('.comment', this._container, (function (comment) {
-                               if (this._comments.has(comment)) {
-                                       return;
-                               }
-                               
-                               if (elDataBool(comment, 'can-edit')) {
-                                       var button = elBySel('.jsCommentEditButton', comment);
-                                       if (button !== null) {
-                                               if (this._callbackClick === null) {
-                                                       this._callbackClick = this._click.bind(this);
-                                               }
-                                               
-                                               button.addEventListener(WCF_CLICK_EVENT, this._callbackClick);
-                                       }
-                               }
-                               
-                               this._comments.add(comment);
-                       }).bind(this));
-               },
-               
-               /**
-                * Handles clicks on the edit button.
-                * 
-                * @param       {?Event}        event           event object
-                * @protected
-                */
-               _click: function(event) {
-                       event.preventDefault();
-                       
-                       if (this._activeElement === null) {
-                               this._activeElement = event.currentTarget.closest('.comment');
-                               
-                               this._prepare();
-                               
-                               Ajax.api(this, {
-                                       actionName: 'beginEdit',
-                                       objectIDs: [this._getObjectId(this._activeElement)]
-                               });
-                       }
-                       else {
-                               UiNotification.show('wcf.message.error.editorAlreadyInUse', null, 'warning');
-                       }
-               },
-               
-               /**
-                * Prepares the message for editor display.
-                * 
-                * @protected
-                */
-               _prepare: function() {
-                       this._editorContainer = elCreate('div');
-                       this._editorContainer.className = 'commentEditorContainer';
-                       this._editorContainer.innerHTML = '<span class="icon icon48 fa-spinner"></span>';
-                       
-                       var content = elBySel('.commentContentContainer', this._activeElement);
-                       content.insertBefore(this._editorContainer, content.firstChild);
-               },
-               
-               /**
-                * Shows the message editor.
-                * 
-                * @param       {Object}        data            ajax response data
-                * @protected
-                */
-               _showEditor: function(data) {
-                       var id = this._getEditorId();
-                       
-                       var icon = elBySel('.icon', this._editorContainer);
-                       elRemove(icon);
-                       
-                       var editor = elCreate('div');
-                       editor.className = 'editorContainer';
-                       //noinspection JSUnresolvedVariable
-                       DomUtil.setInnerHtml(editor, data.returnValues.template);
-                       this._editorContainer.appendChild(editor);
-                       
-                       // bind buttons
-                       var formSubmit = elBySel('.formSubmit', editor);
-                       
-                       var buttonSave = elBySel('button[data-type="save"]', formSubmit);
-                       buttonSave.addEventListener(WCF_CLICK_EVENT, this._save.bind(this));
-                       
-                       var buttonCancel = elBySel('button[data-type="cancel"]', formSubmit);
-                       buttonCancel.addEventListener(WCF_CLICK_EVENT, this._restoreMessage.bind(this));
-                       
-                       EventHandler.add('com.woltlab.wcf.redactor', 'submitEditor_' + id, (function(data) {
-                               data.cancel = true;
-                               
-                               this._save();
-                       }).bind(this));
-                       
-                       var editorElement = elById(id);
-                       if (Environment.editor() === 'redactor') {
-                               window.setTimeout((function() {
-                                       UiScroll.element(this._activeElement);
-                               }).bind(this), 250);
-                       }
-                       else {
-                               editorElement.focus();
-                       }
-               },
-               
-               /**
-                * Restores the message view.
-                * 
-                * @protected
-                */
-               _restoreMessage: function() {
-                       this._destroyEditor();
-                       
-                       elRemove(this._editorContainer);
-                       
-                       this._activeElement = null;
-               },
-               
-               /**
-                * Saves the editor message.
-                * 
-                * @protected
-                */
-               _save: function() {
-                       var parameters = {
-                               data: {
-                                       message: ''
-                               }
-                       };
-                       
-                       var id = this._getEditorId();
-                       
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'getText_' + id, parameters.data);
-                       
-                       if (!this._validate(parameters)) {
-                               // validation failed
-                               return;
-                       }
-                       
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'submit_' + id, parameters);
-                       
-                       Ajax.api(this, {
-                               actionName: 'save',
-                               objectIDs: [this._getObjectId(this._activeElement)],
-                               parameters: parameters
-                       });
-                       
-                       this._hideEditor();
-               },
-               
-               /**
-                * Validates the message and invokes listeners to perform additional validation.
-                *
-                * @param       {Object}        parameters      request parameters
-                * @return      {boolean}       validation result
-                * @protected
-                */
-               _validate: function(parameters) {
-                       // remove all existing error elements
-                       elBySelAll('.innerError', this._activeElement, elRemove);
-                       
-                       // check if editor contains actual content
-                       var editorElement = elById(this._getEditorId());
-                       if (window.jQuery(editorElement).data('redactor').utils.isEmpty()) {
-                               this.throwError(editorElement, Language.get('wcf.global.form.error.empty'));
-                               return false;
-                       }
-                       
-                       var data = {
-                               api: this,
-                               parameters: parameters,
-                               valid: true
-                       };
-                       
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'validate_' + this._getEditorId(), data);
-                       
-                       return (data.valid !== false);
-               },
-               
-               /**
-                * Throws an error by adding an inline error to target element.
-                *
-                * @param       {Element}       element         erroneous element
-                * @param       {string}        message         error message
-                */
-               throwError: function(element, message) {
-                       elInnerError(element, message);
-               },
-               
-               /**
-                * Shows the update message.
-                * 
-                * @param       {Object}        data            ajax response data
-                * @protected
-                */
-               _showMessage: function(data) {
-                       // set new content
-                       //noinspection JSCheckFunctionSignatures
-                       DomUtil.setInnerHtml(elBySel('.commentContent .userMessage', this._editorContainer.parentNode), data.returnValues.message);
-                       
-                       this._restoreMessage();
-                       
-                       UiNotification.show();
-               },
-               
-               /**
-                * Hides the editor from view.
-                * 
-                * @protected
-                */
-               _hideEditor: function() {
-                       elHide(elBySel('.editorContainer', this._editorContainer));
-                       
-                       var icon = elCreate('span');
-                       icon.className = 'icon icon48 fa-spinner';
-                       this._editorContainer.appendChild(icon);
-               },
-               
-               /**
-                * Restores the previously hidden editor.
-                * 
-                * @protected
-                */
-               _restoreEditor: function() {
-                       var icon = elBySel('.fa-spinner', this._editorContainer);
-                       elRemove(icon);
-                       
-                       var editorContainer = elBySel('.editorContainer', this._editorContainer);
-                       if (editorContainer !== null) elShow(editorContainer);
-               },
-               
-               /**
-                * Destroys the editor instance.
-                * 
-                * @protected
-                */
-               _destroyEditor: function() {
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'autosaveDestroy_' + this._getEditorId());
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'destroy_' + this._getEditorId());
-               },
-               
-               /**
-                * Returns the unique editor id.
-                * 
-                * @return      {string}        editor id
-                * @protected
-                */
-               _getEditorId: function() {
-                       return 'commentEditor' + this._getObjectId(this._activeElement);
-               },
-               
-               /**
-                * Returns the element's `data-object-id` value.
-                * 
-                * @param       {Element}       element         target element
-                * @return      {int}
-                * @protected
-                */
-               _getObjectId: function(element) {
-                       return ~~elData(element, 'object-id');
-               },
-               
-               _ajaxFailure: function(data) {
-                       var editor = elBySel('.redactor-layer', this._editorContainer);
-                       
-                       // handle errors occurring on editor load
-                       if (editor === null) {
-                               this._restoreMessage();
-                               
-                               return true;
-                       }
-                       
-                       this._restoreEditor();
-                       
-                       //noinspection JSUnresolvedVariable
-                       if (!data || data.returnValues === undefined || data.returnValues.errorType === undefined) {
-                               return true;
-                       }
-                       
-                       //noinspection JSUnresolvedVariable
-                       elInnerError(editor, data.returnValues.errorType);
-                       
-                       return false;
-               },
-               
-               _ajaxSuccess: function(data) {
-                       switch (data.actionName) {
-                               case 'beginEdit':
-                                       this._showEditor(data);
-                                       break;
-                                       
-                               case 'save':
-                                       this._showMessage(data);
-                                       break;
-                       }
-               },
-               
-               _ajaxSetup: function() {
-                       var objectTypeId = ~~elData(this._container, 'object-type-id');
-                       
-                       return {
-                               data: {
-                                       className: 'wcf\\data\\comment\\CommentAction',
-                                       parameters: {
-                                               data: {
-                                                       objectTypeID: objectTypeId
-                                               }
-                                       }
-                               },
-                               silent: true
-                       };
-               }
-       };
-       
-       return UiCommentEdit;
-});
-
-/**
- * Simplified and consistent dropdown creation.
- * 
- * @author      Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module      WoltLabSuite/Core/Ui/Dropdown/Builder
- */
-define('WoltLabSuite/Core/Ui/Dropdown/Builder',['Core', 'Ui/SimpleDropdown'], function (Core, UiSimpleDropdown) {
-       "use strict";
-       
-       var _validIconSizes = [16, 24, 32, 48, 64, 96, 144];
-       
-       function _validateList(list) {
-               if (!(list instanceof HTMLUListElement)) {
-                       throw new TypeError('Expected a reference to an <ul> element.');
-               }
-               
-               if (!list.classList.contains('dropdownMenu')) {
-                       throw new Error('List does not appear to be a dropdown menu.');
-               }
-       }
-       
-       function _buildItem(data) {
-               var item = elCreate('li');
-               
-               // handle special `divider` type
-               if (data === 'divider') {
-                       item.className = 'dropdownDivider';
-                       return item;
-               }
-               
-               if (typeof data.identifier === 'string') {
-                       elData(item, 'identifier', data.identifier);
-               }
-               
-               var link = elCreate('a');
-               link.href = (typeof data.href === 'string') ? data.href : '#';
-               if (typeof data.callback === 'function') {
-                       link.addEventListener(WCF_CLICK_EVENT, function (event) {
-                               event.preventDefault();
-                               
-                               data.callback(link);
-                       });
-               }
-               else if (link.getAttribute('href') === '#') {
-                       throw new Error('Expected either a `href` value or a `callback`.');
-               }
-               
-               if (data.hasOwnProperty('attributes') && Core.isPlainObject(data.attributes)) {
-                       for (var key in data.attributes) {
-                               if (data.attributes.hasOwnProperty(key)) {
-                                       elData(link, key, data.attributes[key]);
-                               }
-                       }
-               }
-               
-               item.appendChild(link);
-               
-               if (typeof data.icon !== 'undefined' && Core.isPlainObject(data.icon)) {
-                       if (typeof data.icon.name !== 'string') {
-                               throw new TypeError('Expected a valid icon name.');
-                       }
-                       
-                       var size = 16;
-                       if (typeof data.icon.size === 'number' && _validIconSizes.indexOf(~~data.icon.size) !== -1) {
-                               size = ~~data.icon.size;
-                       }
-                       
-                       var icon = elCreate('span');
-                       icon.className = 'icon icon' + size + ' fa-' + data.icon.name;
-                       
-                       link.appendChild(icon);
-               }
-               
-               var label = (typeof data.label === 'string') ? data.label.trim() : '';
-               var labelHtml = (typeof data.labelHtml === 'string') ? data.labelHtml.trim() : '';
-               if (label === '' && labelHtml === '') {
-                       throw new TypeError('Expected either a label or a `labelHtml`.');
-               }
-               
-               var span = elCreate('span');
-               span[label ? 'textContent' : 'innerHTML'] = (label) ? label : labelHtml;
-               link.appendChild(document.createTextNode(' '));
-               link.appendChild(span);
-               
-               return item;
-       }
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Dropdown/Builder
-        */
-       return {
-               /**
-                * Creates a new dropdown menu, optionally pre-populated with the supplied list of
-                * dropdown items. The list element will be returned and must be manually injected
-                * into the DOM by the callee.
-                * 
-                * @param       {(Object|string)[]}     items
-                * @param       {string?}               identifier
-                * @return      {Element}
-                */
-               create: function (items, identifier) {
-                       var list = elCreate('ul');
-                       list.className = 'dropdownMenu';
-                       if (typeof identifier === 'string') {
-                               elData(list, 'identifier', identifier);
-                       }
-                       
-                       if (Array.isArray(items) && items.length > 0) {
-                               this.appendItems(list, items);
-                       }
-                       
-                       return list;
-               },
-               
-               /**
-                * Creates a new dropdown item that can be inserted into lists using regular DOM operations.
-                * 
-                * @param       {(Object|string)}        item
-                * @return      {Element}
-                */
-               buildItem: function (item) {
-                       return _buildItem(item);
-               },
-               
-               /**
-                * Appends a single item to the target list.
-                * 
-                * @param       {Element}               list
-                * @param       {(Object|string)}       item
-                */
-               appendItem: function (list, item) {
-                       _validateList(list);
-                       
-                       list.appendChild(_buildItem(item));
-               },
-               
-               /**
-                * Appends a list of items to the target list.
-                * 
-                * @param       {Element}               list
-                * @param       {(Object|string)[]}     items
-                */
-               appendItems: function (list, items) {
-                       _validateList(list);
-                       
-                       if (!Array.isArray(items)) {
-                               throw new TypeError('Expected an array of items.');
-                       }
-                       
-                       var length = items.length;
-                       if (length === 0) {
-                               throw new Error('Expected a non-empty list of items.');
-                       }
-                       
-                       if (length === 1) {
-                               this.appendItem(list, items[0]);
-                       }
-                       else {
-                               var fragment = document.createDocumentFragment();
-                               for (var i = 0; i < length; i++) {
-                                       fragment.appendChild(_buildItem(items[i]));
-                               }
-                               list.appendChild(fragment);
-                       }
-               },
-               
-               /**
-                * Replaces the existing list items with the provided list of new items.
-                * 
-                * @param       {Element}               list
-                * @param       {(Object|string)[]}     items
-                */
-               setItems: function (list, items) {
-                       _validateList(list);
-                       
-                       list.innerHTML = '';
-                       
-                       this.appendItems(list, items);
-               },
-               
-               /**
-                * Attaches the list to a button, visibility is from then on controlled through clicks
-                * on the provided button element. Internally calls `Ui/SimpleDropdown.initFragment()`
-                * to delegate the DOM management.
-                * 
-                * @param       {Element}               list
-                * @param       {Element}               button
-                */
-               attach: function (list, button) {
-                       _validateList(list);
-                       
-                       UiSimpleDropdown.initFragment(button, list);
-                       
-                       button.addEventListener(WCF_CLICK_EVENT, function (event) {
-                               event.preventDefault();
-                               event.stopPropagation();
-                               
-                               UiSimpleDropdown.toggleDropdown(button.id);
-                       });
-               },
-               
-               /**
-                * Helper method that returns the special string `"divider"` that causes a divider to
-                * be created.
-                * 
-                * @return      {string}
-                */
-               divider: function () {
-                       return 'divider';
-               }
-       };
-});
-
-/**
- * Delete files which are uploaded via AJAX.
- *
- * @author     Joshua Ruesweg
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/File/Delete
- * @since      5.2
- */
-define('WoltLabSuite/Core/Ui/File/Delete',['Ajax', 'Core', 'Dom/ChangeListener', 'Language', 'Dom/Util', 'Dom/Traverse', 'Dictionary'], function(Ajax, Core, DomChangeListener, Language, DomUtil, DomTraverse, Dictionary) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function Delete(buttonContainerId, targetId, isSingleImagePreview, uploadHandler) {
-               this._isSingleImagePreview = isSingleImagePreview;
-               this._uploadHandler = uploadHandler;
-               
-               this._buttonContainer = elById(buttonContainerId);
-               if (this._buttonContainer === null) {
-                       throw new Error("Element id '" + buttonContainerId + "' is unknown.");
-               }
-               
-               this._target = elById(targetId);
-               if (targetId === null) {
-                       throw new Error("Element id '" + targetId + "' is unknown.");
-               }
-               this._containers = new Dictionary();
-               
-               this._internalId = elData(this._target, 'internal-id');
-               
-               if (!this._internalId) {
-                       throw new Error("InternalId is unknown.");
-               }
-               
-               this.rebuild();
-       }
-       
-       Delete.prototype = {
-               /**
-                * Creates the upload button.
-                */
-               _createButtons: function() {
-                       var element, elements = elBySelAll('li.uploadedFile', this._target), elementData, triggerChange = false, uniqueFileId;
-                       for (var i = 0, length = elements.length; i < length; i++) {
-                               element = elements[i];
-                               uniqueFileId = elData(element, 'unique-file-id');
-                               if (this._containers.has(uniqueFileId)) {
-                                       continue;
-                               }
-                               
-                               elementData = {
-                                       uniqueFileId: uniqueFileId,
-                                       element: element
-                               };
-                               
-                               this._containers.set(uniqueFileId, elementData);
-                               this._initDeleteButton(element, elementData);
-                               
-                               triggerChange = true;
-                       }
-                       
-                       if (triggerChange) {
-                               DomChangeListener.trigger();
-                       }
-               },
-               
-               /**
-                * Init the delete button for a specific element.
-                * 
-                * @param       {HTMLElement}   element
-                * @param       {string}        elementData
-                */
-               _initDeleteButton: function(element, elementData) {
-                       var buttonGroup = elBySel('.buttonGroup', element);
-                       
-                       if (buttonGroup === null) {
-                               throw new Error("Button group in '" + targetId + "' is unknown.");
-                       }
-                       
-                       var li = elCreate('li');
-                       var span = elCreate('span');
-                       span.classList = "button jsDeleteButton small";
-                       span.textContent = Language.get('wcf.global.button.delete');
-                       li.appendChild(span);
-                       buttonGroup.appendChild(li);
-                       
-                       li.addEventListener(WCF_CLICK_EVENT, this._delete.bind(this, elementData.uniqueFileId));
-               },
-               
-               /**
-                * Delete a specific file with the given uniqueFileId.
-                * 
-                * @param       {string}        uniqueFileId
-                */
-               _delete: function(uniqueFileId) {
-                       Ajax.api(this, {
-                               uniqueFileId: uniqueFileId,
-                               internalId: this._internalId
-                       });
-               },
-               
-               /**
-                * Rebuilds the delete buttons for unknown files. 
-                */
-               rebuild: function() {
-                       if (this._isSingleImagePreview) {
-                               var img = elBySel('img', this._target);
-                               
-                               if (img !== null) {
-                                       var uniqueFileId = elData(img, 'unique-file-id');
-                                       
-                                       if (!this._containers.has(uniqueFileId)) {
-                                               var elementData = {
-                                                       uniqueFileId: uniqueFileId,
-                                                       element: img
-                                               };
-                                               
-                                               this._containers.set(uniqueFileId, elementData);
-                                               
-                                               this._deleteButton = elCreate('p');
-                                               this._deleteButton.className = 'button deleteButton';
-                                               
-                                               var span = elCreate('span');
-                                               span.textContent = Language.get('wcf.global.button.delete');
-                                               this._deleteButton.appendChild(span);
-                                               
-                                               this._buttonContainer.appendChild(this._deleteButton);
-                                               
-                                               this._deleteButton.addEventListener(WCF_CLICK_EVENT, this._delete.bind(this, elementData.uniqueFileId));
-                                       }
-                               }
-                       }
-                       else {
-                               this._createButtons();
-                       }
-               },
-               
-               _ajaxSuccess: function(data) {
-                       elRemove(this._containers.get(data.uniqueFileId).element);
-                       
-                       if (this._isSingleImagePreview) {
-                               elRemove(this._deleteButton);
-                               this._deleteButton = null;
-                       }
-                       
-                       this._uploadHandler.checkMaxFiles();
-               },
-               
-               _ajaxSetup: function () {
-                       return {
-                               url: 'index.php?ajax-file-delete/&t=' + SECURITY_TOKEN
-                       };
-               }
-       };
-       
-       return Delete;
-});
-
-/**
- * Uploads file via AJAX.
- *
- * @author     Joshua Ruesweg, Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/File/Upload
- * @since      5.2
- */
-define('WoltLabSuite/Core/Ui/File/Upload',['Core', 'Language', 'Dom/Util', 'WoltLabSuite/Core/Ui/File/Delete', 'Upload'], function(Core, Language, DomUtil, DeleteHandler, CoreUpload) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function Upload(buttonContainerId, targetId, options) {
-               options = options || {};
-               
-               if (options.internalId === undefined) {
-                       throw new Error("Missing internal id.");
-               }
-               
-               // set default options
-               this._options = Core.extend({
-                       // name if the upload field
-                       name: '__files[]',
-                       // is true if every file from a multi-file selection is uploaded in its own request
-                       singleFileRequests: false,
-                       // url for uploading file
-                       url: 'index.php?ajax-file-upload/&t=' + SECURITY_TOKEN,
-                       // image preview
-                       imagePreview: false,
-                       // max files
-                       maxFiles: null
-               }, options);
-               
-               this._options.multiple = this._options.maxFiles === null || this._options.maxFiles > 1; 
-               
-               this._options.url = this._options.url;
-               if (this._options.url.indexOf('index.php') === 0) {
-                       this._options.url = WSC_API_URL + this._options.url;
-               }
-               
-               this._buttonContainer = elById(buttonContainerId);
-               if (this._buttonContainer === null) {
-                       throw new Error("Element id '" + buttonContainerId + "' is unknown.");
-               }
-               
-               this._target = elById(targetId);
-               if (targetId === null) {
-                       throw new Error("Element id '" + targetId + "' is unknown.");
-               }
-               
-               if (options.multiple && this._target.nodeName !== 'UL' && this._target.nodeName !== 'OL') {
-                       throw new Error("Target element has to be list or table body if uploading multiple files is supported.");
-               }
-               
-               this._fileElements = [];
-               this._internalFileId = 0;
-               
-               // upload ids that belong to an upload of multiple files at once
-               this._multiFileUploadIds = [];
-               
-               this._createButton();
-               this.checkMaxFiles();
-               
-               this._deleteHandler = new DeleteHandler(buttonContainerId, targetId, this._options.imagePreview, this);
-       }
-       
-       Core.inherit(Upload, CoreUpload, {
-               _createFileElement: function(file) {
-                       var element = Upload._super.prototype._createFileElement.call(this, file);
-                       element.classList.add('box64', 'uploadedFile');
-                       
-                       var progress = elBySel('progress', element);
-                       
-                       var icon = elCreate('span');
-                       icon.classList = 'icon icon64 fa-spinner';
-                       
-                       var fileName = element.textContent;
-                       element.textContent = "";
-                       element.append(icon);
-                       
-                       var innerDiv = elCreate('div');
-                       var fileNameP = elCreate('p');
-                       fileNameP.textContent = fileName; // file.name
-                       
-                       var smallProgress = elCreate('small');
-                       smallProgress.appendChild(progress);
-                       
-                       innerDiv.appendChild(fileNameP);
-                       innerDiv.appendChild(smallProgress);
-                       
-                       var div = elCreate('div');
-                       div.appendChild(innerDiv);
-                       
-                       var ul = elCreate('ul');
-                       ul.classList = 'buttonGroup';
-                       div.appendChild(ul);
-                       
-                       // reset element textContent and replace with own element style
-                       element.append(div);
-                       
-                       return element;
-               },
-               
-               _failure: function(uploadId, data, responseText, xhr, requestOptions) {
-                       for (var i = 0, length = this._fileElements[uploadId].length; i < length; i++) {
-                               this._fileElements[uploadId][i].classList.add('uploadFailed');
-                               
-                               elBySel('small', this._fileElements[uploadId][i]).innerHTML = '';
-                               var icon = elBySel('.icon', this._fileElements[uploadId][i]);
-                               icon.classList.remove('fa-spinner');
-                               icon.classList.add('fa-ban');
-                               
-                               var innerError = elCreate('span');
-                               innerError.classList = 'innerError';
-                               innerError.textContent = Language.get('wcf.upload.error.uploadFailed');
-                               DomUtil.insertAfter(innerError, elBySel('small', this._fileElements[uploadId][i]));
-                       }
-                       
-                       throw new Error("Upload failed: " + data.message);
-               },
-               
-               _upload: function(event, file, blob) {
-                       var innerError = elBySel('small.innerError:not(.innerFileError)', this._buttonContainer.parentNode);
-                       if (innerError) elRemove(innerError);
-                       
-                       return Upload._super.prototype._upload.call(this, event, file, blob);
-               },
-               
-               _success: function(uploadId, data, responseText, xhr, requestOptions) {
-                       for (var i = 0, length = this._fileElements[uploadId].length; i < length; i++) {
-                               if (data['files'][i] !== undefined) {
-                                       if (this._options.imagePreview) {
-                                               if (data['files'][i].image === null) {
-                                                       throw new Error("Expect image for uploaded file. None given.");
-                                               }
-                                               
-                                               elRemove(this._fileElements[uploadId][i]);
-                                               
-                                               if (elBySel('img.previewImage', this._target) !== null) {
-                                                       elBySel('img.previewImage', this._target).setAttribute('src', data['files'][i].image);
-                                               }
-                                               else {
-                                                       var image = elCreate('img');
-                                                       image.classList.add('previewImage');
-                                                       image.setAttribute('src', data['files'][i].image);
-                                                       image.setAttribute('style', "max-width: 100%;");
-                                                       elData(image, 'unique-file-id', data['files'][i].uniqueFileId);
-                                                       this._target.appendChild(image);
-                                               }
-                                       }
-                                       else {
-                                               elData(this._fileElements[uploadId][i], 'unique-file-id', data['files'][i].uniqueFileId);
-                                               elBySel('small', this._fileElements[uploadId][i]).textContent = data['files'][i].filesize;
-                                               var icon = elBySel('.icon', this._fileElements[uploadId][i]);
-                                               icon.classList.remove('fa-spinner');
-                                               icon.classList.add('fa-' + data['files'][i].icon);
-                                       }
-                               }
-                               else if (data['error'][i] !== undefined) {
-                                       this._fileElements[uploadId][i].classList.add('uploadFailed');
-                                       
-                                       elBySel('small', this._fileElements[uploadId][i]).innerHTML = '';
-                                       var icon = elBySel('.icon', this._fileElements[uploadId][i]);
-                                       icon.classList.remove('fa-spinner');
-                                       icon.classList.add('fa-ban');
-                                       
-                                       if (elBySel('.innerError', this._fileElements[uploadId][i]) === null) {
-                                               var innerError = elCreate('span');
-                                               innerError.classList = 'innerError';
-                                               innerError.textContent = data['error'][i].errorMessage;
-                                               DomUtil.insertAfter(innerError, elBySel('small', this._fileElements[uploadId][i]));
-                                       }
-                                       else {
-                                               elBySel('.innerError', this._fileElements[uploadId][i]).textContent = data['error'][i].errorMessage;
-                                       }
-                               }
-                               else {
-                                       throw new Error('Unknown uploaded file for uploadId ' + uploadId + '.');
-                               }
-                       }
-                       
-                       // create delete buttons
-                       this._deleteHandler.rebuild();
-                       this.checkMaxFiles();
-               },
-               
-               _getFormData: function() {
-                       return {
-                               internalId: this._options.internalId
-                       };
-               },
-               
-               validateUpload: function(files) {
-                       if (this._options.maxFiles === null || files.length + this.countFiles() <= this._options.maxFiles) {
-                               return true;
-                       }
-                       else {
-                               var innerError = elBySel('small.innerError:not(.innerFileError)', this._buttonContainer.parentNode);
-                               
-                               if (innerError === null) {
-                                       innerError = elCreate('small');
-                                       innerError.classList = 'innerError';
-                                       DomUtil.insertAfter(innerError, this._buttonContainer);
-                               }
-                               
-                               innerError.textContent = Language.get('wcf.upload.error.reachedRemainingLimit', {
-                                       maxFiles: this._options.maxFiles - this.countFiles()
-                               });
-                               
-                               return false;
-                       }
-               },
-               
-               /**
-                * Returns the count of the uploaded images.
-                * 
-                * @return {int}
-                */
-               countFiles: function() {
-                       if (this._options.imagePreview) {
-                               return elBySel('img', this._target) !== null ? 1 : 0;
-                       }
-                       else {
-                               return this._target.childElementCount;
-                       }
-               },
-               
-               /**
-                * Checks the maximum number of files and enables or disables the upload button.
-                */
-               checkMaxFiles: function() {
-                       if (this._options.maxFiles !== null && this.countFiles() >= this._options.maxFiles) {
-                               elHide(this._button);
-                       }
-                       else {
-                               elShow(this._button);
-                       }
-               }
-       });
-       
-       return Upload;
-});
-
-/**
- * Provides a filter input for checkbox lists.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/ItemList/Filter
- */
-define('WoltLabSuite/Core/Ui/ItemList/Filter',['Core', 'EventKey', 'Language', 'List', 'StringUtil', 'Dom/Util', 'Ui/SimpleDropdown'], function (Core, EventKey, Language, List, StringUtil, DomUtil, UiSimpleDropdown) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _buildItems: function() {},
-                       _prepareItem: function() {},
-                       _keyup: function() {},
-                       _toggleVisibility: function () {},
-                       _setupVisibilityFilter: function () {},
-                       _setVisibility: function () {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * Creates a new filter input.
-        * 
-        * @param       {string}        elementId       list element id
-        * @param       {Object=}       options         options
-        * @constructor
-        */
-       function UiItemListFilter(elementId, options) { this.init(elementId, options); }
-       UiItemListFilter.prototype = {
-               /**
-                * Creates a new filter input.
-                * 
-                * @param       {string}        elementId       list element id
-                * @param       {Object=}       options         options
-                */
-               init: function(elementId, options) {
-                       this._value = '';
-                       
-                       this._options = Core.extend({
-                               callbackPrepareItem: undefined,
-                               enableVisibilityFilter: true
-                       }, options);
-                       
-                       var element = elById(elementId);
-                       if (element === null) {
-                               throw new Error("Expected a valid element id, '" + elementId + "' does not match anything.");
-                       }
-                       else if (!element.classList.contains('scrollableCheckboxList') && typeof this._options.callbackPrepareItem !== 'function') {
-                               throw new Error("Filter only works with elements with the CSS class 'scrollableCheckboxList'.");
-                       }
-                       
-                       elData(element, 'filter', 'showAll');
-                       
-                       var container = elCreate('div');
-                       container.className = 'itemListFilter';
-                       
-                       element.parentNode.insertBefore(container, element);
-                       container.appendChild(element);
-                       
-                       var inputAddon = elCreate('div');
-                       inputAddon.className = 'inputAddon';
-                       
-                       var input = elCreate('input');
-                       input.className = 'long';
-                       input.type = 'text';
-                       input.placeholder = Language.get('wcf.global.filter.placeholder');
-                       input.addEventListener('keydown', function (event) {
-                               if (EventKey.Enter(event)) {
-                                       event.preventDefault();
-                               }
-                       });
-                       input.addEventListener('keyup', this._keyup.bind(this));
-                       
-                       var clearButton = elCreate('a');
-                       clearButton.href = '#';
-                       clearButton.className = 'button inputSuffix jsTooltip';
-                       clearButton.title = Language.get('wcf.global.filter.button.clear');
-                       clearButton.innerHTML = '<span class="icon icon16 fa-times"></span>';
-                       clearButton.addEventListener('click', (function(event) {
-                               event.preventDefault();
-                               
-                               this.reset();
-                       }).bind(this));
-                       
-                       inputAddon.appendChild(input);
-                       inputAddon.appendChild(clearButton);
-                       
-                       if (this._options.enableVisibilityFilter) {
-                               var visibilityButton = elCreate('a');
-                               visibilityButton.href = '#';
-                               visibilityButton.className = 'button inputSuffix jsTooltip';
-                               visibilityButton.title = Language.get('wcf.global.filter.button.visibility');
-                               visibilityButton.innerHTML = '<span class="icon icon16 fa-eye"></span>';
-                               visibilityButton.addEventListener(WCF_CLICK_EVENT, this._toggleVisibility.bind(this));
-                               inputAddon.appendChild(visibilityButton);
-                       }
-                       
-                       container.appendChild(inputAddon);
-                       
-                       this._container = container;
-                       this._dropdown = null;
-                       this._dropdownId = '';
-                       this._element = element;
-                       this._input = input;
-                       this._items = null;
-                       this._fragment = null;
-               },
-               
-               /**
-                * Resets the filter.
-                */
-               reset: function () {
-                       this._input.value = '';
-                       this._keyup();
-               },
-               
-               /**
-                * Builds the item list and rebuilds the items' DOM for easier manipulation.
-                * 
-                * @protected
-                */
-               _buildItems: function() {
-                       this._items = new List();
-                       
-                       var callback = (typeof this._options.callbackPrepareItem === 'function') ? this._options.callbackPrepareItem : this._prepareItem.bind(this);
-                       for (var i = 0, length = this._element.childElementCount; i < length; i++) {
-                               this._items.add(callback(this._element.children[i]));
-                       }
-               },
-               
-               /**
-                * Processes an item and returns the meta data.
-                * 
-                * @param       {Element}       item    current item
-                * @return      {{item: *, span: Element, text: string}}
-                * @protected
-                */
-               _prepareItem: function(item) {
-                       var label = item.children[0];
-                       var text = label.textContent.trim();
-                       
-                       var checkbox = label.children[0];
-                       while (checkbox.nextSibling) {
-                               label.removeChild(checkbox.nextSibling);
-                       }
-                       
-                       label.appendChild(document.createTextNode(' '));
-                       
-                       var span = elCreate('span');
-                       span.textContent = text;
-                       label.appendChild(span);
-                       
-                       return {
-                               item: item,
-                               span: span,
-                               text: text
-                       };
-               },
-               
-               /**
-                * Rebuilds the list on keyup, uses case-insensitive matching.
-                * 
-                * @protected
-                */
-               _keyup: function() {
-                       var value = this._input.value.trim();
-                       if (this._value === value) {
-                               return;
-                       }
-                       
-                       if (this._fragment === null) {
-                               this._fragment = document.createDocumentFragment();
-                               
-                               // set fixed height to avoid layout jumps
-                               this._element.style.setProperty('height', this._element.offsetHeight + 'px', '');
-                       }
-                       
-                       // move list into fragment before editing items, increases performance
-                       // by avoiding the browser to perform repaint/layout over and over again
-                       this._fragment.appendChild(this._element);
-                       
-                       if (this._items === null) {
-                               this._buildItems();
-                       }
-                       
-                       var regexp = new RegExp('(' + StringUtil.escapeRegExp(value) + ')', 'i');
-                       var hasVisibleItems = (value === '');
-                       this._items.forEach(function (item) {
-                               if (value === '') {
-                                       item.span.textContent = item.text;
-                                       
-                                       elShow(item.item);
-                               }
-                               else {
-                                       if (regexp.test(item.text)) {
-                                               item.span.innerHTML = item.text.replace(regexp, '<u>$1</u>');
-                                               
-                                               elShow(item.item);
-                                               hasVisibleItems = true;
-                                       }
-                                       else {
-                                               elHide(item.item);
-                                       }
-                               }
-                       });
-                       
-                       this._container.insertBefore(this._fragment.firstChild, this._container.firstChild);
-                       this._value = value;
-                       
-                       elInnerError(this._container, (hasVisibleItems) ? false : Language.get('wcf.global.filter.error.noMatches'));
-               },
-               
-               /**
-                * Toggles the visibility mode for marked items.
-                *
-                * @param       {Event}         event
-                * @protected
-                */
-               _toggleVisibility: function (event) {
-                       event.preventDefault();
-                       event.stopPropagation();
-                       
-                       var button = event.currentTarget;
-                       if (this._dropdown === null) {
-                               var dropdown = elCreate('ul');
-                               dropdown.className = 'dropdownMenu';
-                               
-                               ['activeOnly', 'highlightActive', 'showAll'].forEach((function (type) {
-                                       var link = elCreate('a');
-                                       elData(link, 'type', type);
-                                       link.href = '#';
-                                       link.textContent = Language.get('wcf.global.filter.visibility.' + type);
-                                       link.addEventListener(WCF_CLICK_EVENT, this._setVisibility.bind(this));
-                                       
-                                       var li = elCreate('li');
-                                       li.appendChild(link);
-                                       
-                                       if (type === 'showAll') {
-                                               li.className = 'active';
-                                               
-                                               var divider = elCreate('li');
-                                               divider.className = 'dropdownDivider';
-                                               dropdown.appendChild(divider);
-                                       }
-                                       
-                                       dropdown.appendChild(li);
-                               }).bind(this));
-                               
-                               UiSimpleDropdown.initFragment(button, dropdown);
-                               
-                               // add `active` classes required for the visibility filter
-                               this._setupVisibilityFilter();
-                               
-                               this._dropdown = dropdown;
-                               this._dropdownId = button.id;
-                       }
-                       
-                       UiSimpleDropdown.toggleDropdown(button.id, button);
-               },
-               
-               /**
-                * Set-ups the visibility filter by assigning an active class to the
-                * list items that hold the checkboxes and observing the checkboxes
-                * for any changes.
-                *
-                * This process involves quite a few DOM changes and new event listeners,
-                * therefore we'll delay this until the filter has been accessed for
-                * the first time, because none of these changes matter before that.
-                *
-                * @protected
-                */
-               _setupVisibilityFilter: function () {
-                       var nextSibling = this._element.nextSibling;
-                       var parent = this._element.parentNode;
-                       var scrollTop = this._element.scrollTop;
-                       
-                       // mass-editing of DOM elements is slow while they're part of the document 
-                       var fragment = document.createDocumentFragment();
-                       fragment.appendChild(this._element);
-                       
-                       elBySelAll('li', this._element, function(li) {
-                               var checkbox = elBySel('input[type="checkbox"]', li);
-                               if (checkbox) {
-                                       if (checkbox.checked) li.classList.add('active');
-                                       
-                                       checkbox.addEventListener('change', function() {
-                                               li.classList[(checkbox.checked ? 'add' : 'remove')]('active');
-                                       });
-                               }
-                               else {
-                                       var radioButton = elBySel('input[type="radio"]', li);
-                                       if (radioButton) {
-                                               if (radioButton.checked) li.classList.add('active');
-                                               
-                                               radioButton.addEventListener('change', function() {
-                                                       elBySelAll('li', this._element, function(everyLi) {
-                                                               everyLi.classList.remove('active');
-                                                       });
-                                                       
-                                                       li.classList[(radioButton.checked ? 'add' : 'remove')]('active');
-                                               }.bind(this));
-                                       }
-                               }
-                       }.bind(this));
-                       
-                       // re-insert the modified DOM
-                       parent.insertBefore(this._element, nextSibling);
-                       this._element.scrollTop = scrollTop;
-               },
-               
-               /**
-                * Sets the visibility of marked items.
-                *
-                * @param       {Event}         event
-                * @protected
-                */
-               _setVisibility: function (event) {
-                       event.preventDefault();
-                       
-                       var link = event.currentTarget;
-                       var type = elData(link, 'type');
-                       
-                       UiSimpleDropdown.close(this._dropdownId);
-                       
-                       if (elData(this._element, 'filter') === type) {
-                               // filter did not change
-                               return;
-                       }
-                       
-                       elData(this._element, 'filter', type);
-                       
-                       elBySel('.active', this._dropdown).classList.remove('active');
-                       link.parentNode.classList.add('active');
-                       
-                       var button = elById(this._dropdownId);
-                       button.classList[(type === 'showAll' ? 'remove' : 'add')]('active');
-                       
-                       var icon = elBySel('.icon', button);
-                       icon.classList[(type === 'showAll' ? 'add' : 'remove')]('fa-eye');
-                       icon.classList[(type === 'showAll' ? 'remove' : 'add')]('fa-eye-slash');
-               }
-       };
-       
-       return UiItemListFilter;
-});
-
-/**
- * Flexible UI element featuring both a list of items and an input field.
- * 
- * @author     Alexander Ebert, Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/ItemList/Static
- */
-define('WoltLabSuite/Core/Ui/ItemList/Static',['Core', 'Dictionary', 'Language', 'Dom/Traverse', 'EventKey', 'Ui/SimpleDropdown'], function(Core, Dictionary, Language, DomTraverse, EventKey, UiSimpleDropdown) {
-       "use strict";
-       
-       var _activeId = '';
-       var _data = new Dictionary();
-       var _didInit = false;
-       
-       var _callbackKeyDown = null;
-       var _callbackKeyPress = null;
-       var _callbackKeyUp = null;
-       var _callbackPaste = null;
-       var _callbackRemoveItem = null;
-       var _callbackBlur = null;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/ItemList/Static
-        */
-       return {
-               /**
-                * Initializes an item list.
-                *
-                * The `values` argument must be empty or contain a list of strings or object, e.g.
-                * `['foo', 'bar']` or `[{ objectId: 1337, value: 'baz'}, {...}]`
-                *
-                * @param       {string}        elementId       input element id
-                * @param       {Array}         values          list of existing values
-                * @param       {Object}        options         option list
-                */
-               init: function(elementId, values, options) {
-                       var element = elById(elementId);
-                       if (element === null) {
-                               throw new Error("Expected a valid element id, '" + elementId + "' is invalid.");
-                       }
-                       
-                       // remove data from previous instance
-                       if (_data.has(elementId)) {
-                               var tmp = _data.get(elementId);
-                               
-                               for (var key in tmp) {
-                                       if (tmp.hasOwnProperty(key)) {
-                                               var el = tmp[key];
-                                               if (el instanceof Element && el.parentNode) {
-                                                       elRemove(el);
-                                               }
-                                       }
-                               }
-                               
-                               UiSimpleDropdown.destroy(elementId);
-                               _data.delete(elementId);
-                       }
-                       
-                       options = Core.extend({
-                               // maximum number of items this list may contain, `-1` for infinite
-                               maxItems: -1,
-                               // maximum length of an item value, `-1` for infinite
-                               maxLength: -1,
-                               
-                               // initial value will be interpreted as comma separated value and submitted as such
-                               isCSV: false,
-                               
-                               // will be invoked whenever the items change, receives the element id first and list of values second
-                               callbackChange: null,
-                               // callback once the form is about to be submitted
-                               callbackSubmit: null,
-                               // value may contain the placeholder `{$objectId}`
-                               submitFieldName: ''
-                       }, options);
-                       
-                       var form = DomTraverse.parentByTag(element, 'FORM');
-                       if (form !== null) {
-                               if (options.isCSV === false) {
-                                       if (!options.submitFieldName.length && typeof options.callbackSubmit !== 'function') {
-                                               throw new Error("Expected a valid function for option 'callbackSubmit', a non-empty value for option 'submitFieldName' or enabling the option 'submitFieldCSV'.");
-                                       }
-                                       
-                                       form.addEventListener('submit', (function() {
-                                               var values = this.getValues(elementId);
-                                               if (options.submitFieldName.length) {
-                                                       var input;
-                                                       for (var i = 0, length = values.length; i < length; i++) {
-                                                               input = elCreate('input');
-                                                               input.type = 'hidden';
-                                                               input.name = options.submitFieldName.replace(/{$objectId}/, values[i].objectId);
-                                                               input.value = values[i].value;
-                                                               
-                                                               form.appendChild(input);
-                                                       }
-                                               }
-                                               else {
-                                                       options.callbackSubmit(form, values);
-                                               }
-                                       }).bind(this));
-                               }
-                       }
-                       
-                       this._setup();
-                       
-                       var data = this._createUI(element, options);
-                       _data.set(elementId, {
-                               dropdownMenu: null,
-                               element: data.element,
-                               list: data.list,
-                               listItem: data.element.parentNode,
-                               options: options,
-                               shadow: data.shadow
-                       });
-                       
-                       values = (data.values.length) ? data.values : values;
-                       if (Array.isArray(values)) {
-                               var value;
-                               for (var i = 0, length = values.length; i < length; i++) {
-                                       value = values[i];
-                                       if (typeof value === 'string') {
-                                               value = { objectId: 0, value: value };
-                                       }
-                                       
-                                       this._addItem(elementId, value);
-                               }
-                       }
-               },
-               
-               /**
-                * Returns the list of current values.
-                *
-                * @param       {string}        elementId       input element id
-                * @return      {Array}         list of objects containing object id and value
-                */
-               getValues: function(elementId) {
-                       if (!_data.has(elementId)) {
-                               throw new Error("Element id '" + elementId + "' is unknown.");
-                       }
-                       
-                       var data = _data.get(elementId);
-                       var values = [];
-                       elBySelAll('.item > span', data.list, function(span) {
-                               values.push({
-                                       objectId: ~~elData(span, 'object-id'),
-                                       value: span.textContent
-                               });
-                       });
-                       
-                       return values;
-               },
-               
-               /**
-                * Sets the list of current values.
-                *
-                * @param       {string}        elementId       input element id
-                * @param       {Array}         values          list of objects containing object id and value
-                */
-               setValues: function(elementId, values) {
-                       if (!_data.has(elementId)) {
-                               throw new Error("Element id '" + elementId + "' is unknown.");
-                       }
-                       
-                       var data = _data.get(elementId);
-                       
-                       // remove all existing items first
-                       var i, length;
-                       var items = DomTraverse.childrenByClass(data.list, 'item');
-                       for (i = 0, length = items.length; i < length; i++) {
-                               this._removeItem(null, items[i], true);
-                       }
-                       
-                       // add new items
-                       for (i = 0, length = values.length; i < length; i++) {
-                               this._addItem(elementId, values[i]);
-                       }
-               },
-               
-               /**
-                * Binds static event listeners.
-                */
-               _setup: function() {
-                       if (_didInit) {
-                               return;
-                       }
-                       
-                       _didInit = true;
-                       
-                       _callbackKeyDown = this._keyDown.bind(this);
-                       _callbackKeyPress = this._keyPress.bind(this);
-                       _callbackKeyUp = this._keyUp.bind(this);
-                       _callbackPaste = this._paste.bind(this);
-                       _callbackRemoveItem = this._removeItem.bind(this);
-                       _callbackBlur = this._blur.bind(this);
-               },
-               
-               /**
-                * Creates the DOM structure for target element. If `element` is a `<textarea>`
-                * it will be automatically replaced with an `<input>` element.
-                *
-                * @param       {Element}       element         input element
-                * @param       {Object}        options         option list
-                */
-               _createUI: function(element, options) {
-                       var list = elCreate('ol');
-                       list.className = 'inputItemList' + (element.disabled ? ' disabled' : '');
-                       elData(list, 'element-id', element.id);
-                       list.addEventListener(WCF_CLICK_EVENT, function(event) {
-                               if (event.target === list) {
-                                       //noinspection JSUnresolvedFunction
-                                       element.focus();
-                               }
-                       });
-                       
-                       var listItem = elCreate('li');
-                       listItem.className = 'input';
-                       list.appendChild(listItem);
-                       
-                       element.addEventListener('keydown', _callbackKeyDown);
-                       element.addEventListener('keypress', _callbackKeyPress);
-                       element.addEventListener('keyup', _callbackKeyUp);
-                       element.addEventListener('paste', _callbackPaste);
-                       element.addEventListener('blur', _callbackBlur);
-                       
-                       element.parentNode.insertBefore(list, element);
-                       listItem.appendChild(element);
-                       
-                       if (options.maxLength !== -1) {
-                               elAttr(element, 'maxLength', options.maxLength);
-                       }
-                       
-                       var shadow = null, values = [];
-                       if (options.isCSV) {
-                               shadow = elCreate('input');
-                               shadow.className = 'itemListInputShadow';
-                               shadow.type = 'hidden';
-                               //noinspection JSUnresolvedVariable
-                               shadow.name = element.name;
-                               element.removeAttribute('name');
-                               
-                               list.parentNode.insertBefore(shadow, list);
-                               
-                               //noinspection JSUnresolvedVariable
-                               var value, tmp = element.value.split(',');
-                               for (var i = 0, length = tmp.length; i < length; i++) {
-                                       value = tmp[i].trim();
-                                       if (value.length) {
-                                               values.push(value);
-                                       }
-                               }
-                               
-                               if (element.nodeName === 'TEXTAREA') {
-                                       var inputElement = elCreate('input');
-                                       inputElement.type = 'text';
-                                       element.parentNode.insertBefore(inputElement, element);
-                                       inputElement.id = element.id;
-                                       
-                                       elRemove(element);
-                                       element = inputElement;
-                               }
-                       }
-                       
-                       return {
-                               element: element,
-                               list: list,
-                               shadow: shadow,
-                               values: values
-                       };
-               },
-               
-               /**
-                * Enforces the maximum number of items.
-                *
-                * @param       {string}        elementId       input element id
-                */
-               _handleLimit: function(elementId) {
-                       var data = _data.get(elementId);
-                       if (data.options.maxItems === -1) {
-                               return;
-                       }
-                       
-                       if (data.list.childElementCount - 1 < data.options.maxItems) {
-                               if (data.element.disabled) {
-                                       data.element.disabled = false;
-                                       data.element.removeAttribute('placeholder');
-                               }
-                       }
-                       else if (!data.element.disabled) {
-                               data.element.disabled = true;
-                               elAttr(data.element, 'placeholder', Language.get('wcf.global.form.input.maxItems'));
-                       }
-               },
-               
-               /**
-                * Sets the active item list id and handles keyboard access to remove an existing item.
-                *
-                * @param       {object}        event           event object
-                */
-               _keyDown: function(event) {
-                       var input = event.currentTarget;
-                       var lastItem = input.parentNode.previousElementSibling;
-                       
-                       _activeId = input.id;
-                       
-                       if (event.keyCode === 8) {
-                               // 8 = [BACKSPACE]
-                               if (input.value.length === 0) {
-                                       if (lastItem !== null) {
-                                               if (lastItem.classList.contains('active')) {
-                                                       this._removeItem(null, lastItem);
-                                               }
-                                               else {
-                                                       lastItem.classList.add('active');
-                                               }
-                                       }
-                               }
-                       }
-                       else if (event.keyCode === 27) {
-                               // 27 = [ESC]
-                               if (lastItem !== null && lastItem.classList.contains('active')) {
-                                       lastItem.classList.remove('active');
-                               }
-                       }
-               },
-               
-               /**
-                * Handles the `[ENTER]` and `[,]` key to add an item to the list.
-                *
-                * @param       {Event}         event           event object
-                */
-               _keyPress: function(event) {
-                       if (EventKey.Enter(event) || EventKey.Comma(event)) {
-                               event.preventDefault();
-                               
-                               var value = event.currentTarget.value.trim();
-                               if (value.length) {
-                                       this._addItem(event.currentTarget.id, { objectId: 0, value: value });
-                               }
-                       }
-               },
-               
-               /**
-                * Splits comma-separated values being pasted into the input field.
-                *
-                * @param       {Event}         event
-                * @protected
-                */
-               _paste: function (event) {
-                       var text = '';
-                       if (typeof window.clipboardData === 'object') {
-                               // IE11
-                               text = window.clipboardData.getData('Text');
-                       }
-                       else {
-                               text = event.clipboardData.getData('text/plain');
-                       }
-                       
-                       text.split(/,/).forEach((function(item) {
-                               item = item.trim();
-                               if (item.length !== 0) {
-                                       this._addItem(event.currentTarget.id, { objectId: 0, value: item });
-                               }
-                       }).bind(this));
-                       
-                       event.preventDefault();
-               },
-               
-               /**
-                * Handles the keyup event to unmark an item for deletion.
-                *
-                * @param       {object}        event           event object
-                */
-               _keyUp: function(event) {
-                       var input = event.currentTarget;
-                       
-                       if (input.value.length > 0) {
-                               var lastItem = input.parentNode.previousElementSibling;
-                               if (lastItem !== null) {
-                                       lastItem.classList.remove('active');
-                               }
-                       }
-               },
-               
-               /**
-                * Adds an item to the list.
-                *
-                * @param       {string}        elementId       input element id
-                * @param       {object}        value           item value
-                */
-               _addItem: function(elementId, value) {
-                       var data = _data.get(elementId);
-                       
-                       var listItem = elCreate('li');
-                       listItem.className = 'item';
-                       
-                       var content = elCreate('span');
-                       content.className = 'content';
-                       elData(content, 'object-id', value.objectId);
-                       content.textContent = value.value;
-                       listItem.appendChild(content);
-                       
-                       if (!data.element.disabled) {
-                               var button = elCreate('a');
-                               button.className = 'icon icon16 fa-times';
-                               button.addEventListener(WCF_CLICK_EVENT, _callbackRemoveItem);
-                               listItem.appendChild(button);
-                       }
-                       
-                       data.list.insertBefore(listItem, data.listItem);
-                       data.element.value = '';
-                       
-                       if (!data.element.disabled) {
-                               this._handleLimit(elementId);
-                       }
-                       var values = this._syncShadow(data);
-                       
-                       if (typeof data.options.callbackChange === 'function') {
-                               if (values === null) values = this.getValues(elementId);
-                               data.options.callbackChange(elementId, values);
-                       }
-               },
-               
-               /**
-                * Removes an item from the list.
-                *
-                * @param       {?object}       event           event object
-                * @param       {Element?}      item            list item
-                * @param       {boolean?}      noFocus         input element will not be focused if true
-                */
-               _removeItem: function(event, item, noFocus) {
-                       item = (event === null) ? item : event.currentTarget.parentNode;
-                       
-                       var parent = item.parentNode;
-                       //noinspection JSCheckFunctionSignatures
-                       var elementId = elData(parent, 'element-id');
-                       var data = _data.get(elementId);
-                       
-                       parent.removeChild(item);
-                       if (!noFocus) data.element.focus();
-                       
-                       this._handleLimit(elementId);
-                       var values = this._syncShadow(data);
-                       
-                       if (typeof data.options.callbackChange === 'function') {
-                               if (values === null) values = this.getValues(elementId);
-                               data.options.callbackChange(elementId, values);
-                       }
-               },
-               
-               /**
-                * Synchronizes the shadow input field with the current list item values.
-                *
-                * @param       {object}        data            element data
-                */
-               _syncShadow: function(data) {
-                       if (!data.options.isCSV) return null;
-                       
-                       var value = '', values = this.getValues(data.element.id);
-                       for (var i = 0, length = values.length; i < length; i++) {
-                               value += (value.length ? ',' : '') + values[i].value;
-                       }
-                       
-                       data.shadow.value = value;
-                       
-                       return values;
-               },
-               
-               /**
-                * Handles the blur event.
-                *
-                * @param       {object}        event           event object
-                */
-               _blur: function(event) {
-                       var data = _data.get(event.currentTarget.id);
-                       
-                       var currentTarget = event.currentTarget;
-                       window.setTimeout(function() {
-                               var value = currentTarget.value.trim();
-                               if (value.length) {
-                                       this._addItem(currentTarget.id, { objectId: 0, value: value });
-                               }
-                       }.bind(this), 100);
-               }
-       };
-});
-
-/**
- * Provides an item list for users and groups.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/ItemList/User
- */
-define('WoltLabSuite/Core/Ui/ItemList/User',['WoltLabSuite/Core/Ui/ItemList'], function(UiItemList) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       getValues: function() {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/ItemList/User
-        */
-       return {
-               _shadowGroups: null,
-               
-               /**
-                * Initializes user suggestion support for an element.
-                * 
-                * @param       {string}        elementId       input element id
-                * @param       {object}        options         option list
-                */
-               init: function(elementId, options) {
-                       this._shadowGroups = null;
-                       
-                       UiItemList.init(elementId, [], {
-                               ajax: {
-                                       className: 'wcf\\data\\user\\UserAction',
-                                       parameters: {
-                                               data: {
-                                                       includeUserGroups: ~~options.includeUserGroups,
-                                                       restrictUserGroupIDs: (Array.isArray(options.restrictUserGroupIDs) ? options.restrictUserGroupIDs : [])
-                                               }
-                                       }
-                               },
-                               callbackChange: (typeof options.callbackChange === 'function' ? options.callbackChange : null),
-                               callbackSyncShadow: options.csvPerType ? this._syncShadow.bind(this) : null,
-                               callbackSetupValues: (typeof options.callbackSetupValues === 'function' ? options.callbackSetupValues : null),
-                               excludedSearchValues: (Array.isArray(options.excludedSearchValues) ? options.excludedSearchValues : []),
-                               isCSV: true,
-                               maxItems: ~~options.maxItems || -1,
-                               restricted: true
-                       });
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Ui/ItemList::getValues()
-                */
-               getValues: function(elementId) {
-                       return UiItemList.getValues(elementId);
-               },
-               
-               _syncShadow: function(data) {
-                       var values = this.getValues(data.element.id);
-                       var users = [], groups = [];
-                       
-                       values.forEach(function(value) {
-                               if (value.type && value.type === 'group') groups.push(value.objectId);
-                               else users.push(value.value);
-                       });
-                       
-                       data.shadow.value = users.join(',');
-                       if (!this._shadowGroups) {
-                               this._shadowGroups = elCreate('input');
-                               this._shadowGroups.type = 'hidden';
-                               this._shadowGroups.name = data.shadow.name + 'GroupIDs';
-                               data.shadow.parentNode.insertBefore(this._shadowGroups, data.shadow);
-                       }
-                       this._shadowGroups.value = groups.join(',');
-                       
-                       return values;
-               }
-       };
-});
-
-/**
- * Object-based user list.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/User/List
- */
-define('WoltLabSuite/Core/Ui/User/List',['Ajax', 'Core', 'Dictionary', 'Dom/Util', 'Ui/Dialog', 'WoltLabSuite/Core/Ui/Pagination'], function(Ajax, Core, Dictionary, DomUtil, UiDialog, UiPagination) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function UiUserList(options) { this.init(options); }
-       UiUserList.prototype = {
-               /**
-                * Initializes the user list.
-                * 
-                * @param       {object}        options         list of initialization options
-                */
-               init: function(options) {
-                       this._cache = new Dictionary();
-                       this._pageCount = 0;
-                       this._pageNo = 1;
-                       
-                       this._options = Core.extend({
-                               className: '',
-                               dialogTitle: '',
-                               parameters: {}
-                       }, options);
-               },
-               
-               /**
-                * Opens the user list.
-                */
-               open: function() {
-                       this._pageNo = 1;
-                       this._showPage();
-               },
-               
-               /**
-                * Shows the current or given page.
-                * 
-                * @param       {int=}          pageNo          page number
-                */
-               _showPage: function(pageNo) {
-                       if (typeof pageNo === 'number') {
-                               this._pageNo = ~~pageNo;
-                       }
-                       
-                       if (this._pageCount !== 0 && (this._pageNo < 1 || this._pageNo > this._pageCount)) {
-                               throw new RangeError("pageNo must be between 1 and " + this._pageCount + " (" + this._pageNo + " given).");
-                       }
-                       
-                       if (this._cache.has(this._pageNo)) {
-                               var dialog = UiDialog.open(this, this._cache.get(this._pageNo));
-                               
-                               if (this._pageCount > 1) {
-                                       var element = elBySel('.jsPagination', dialog.content);
-                                       if (element !== null) {
-                                               new UiPagination(element, {
-                                                       activePage: this._pageNo,
-                                                       maxPage: this._pageCount,
-                                                       
-                                                       callbackSwitch: this._showPage.bind(this)
-                                               });
-                                       }
-                                       
-                                       // scroll to the list start
-                                       var container = dialog.content.parentNode;
-                                       if (container.scrollTop > 0) {
-                                               container.scrollTop = 0;
-                                       }
-                               }
-                       }
-                       else {
-                               this._options.parameters.pageNo = this._pageNo;
-                               
-                               Ajax.api(this, {
-                                       parameters: this._options.parameters
-                               });
-                       }
-               },
-               
-               _ajaxSuccess: function(data) {
-                       if (data.returnValues.pageCount !== undefined) {
-                               this._pageCount = ~~data.returnValues.pageCount;
-                       }
-                       
-                       this._cache.set(this._pageNo, data.returnValues.template);
-                       this._showPage();
-               },
-               
-               _ajaxSetup: function() {
-                       return {
-                               data: {
-                                       actionName: 'getGroupedUserList',
-                                       className: this._options.className,
-                                       interfaceName: 'wcf\\data\\IGroupedUserListAction'
-                               }
-                       };
-               },
-               
-               _dialogSetup: function() {
-                       return {
-                               id: DomUtil.getUniqueId(),
-                               options: {
-                                       title: this._options.dialogTitle
-                               },
-                               source: null
-                       };
-               }
-       };
-       
-       return UiUserList;
-});
-
-/**
- * Provides interface elements to use reactions.
- *
- * @author     Joshua Ruesweg
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Reaction/Handler
- * @since       5.2
- */
-define(
-       'WoltLabSuite/Core/Ui/Reaction/CountButtons',[
-               'Ajax',      'Core',          'Dictionary',         'Language',
-               'ObjectMap', 'StringUtil',    'Dom/ChangeListener', 'Dom/Util',
-               'Ui/Dialog'
-       ],
-       function(
-               Ajax,        Core,                        Dictionary,           Language,
-               ObjectMap,   StringUtil,                  DomChangeListener,    DomUtil,
-               UiDialog
-       )
-       {
-               "use strict";
-               
-               /**
-                * @constructor
-                */
-               function CountButtons(objectType, options) { this.init(objectType, options); }
-               CountButtons.prototype = {
-                       /**
-                        * Initializes the like handler.
-                        *
-                        * @param       {string}        objectType      object type
-                        * @param       {object}        options         initialization options
-                        */
-                       init: function(objectType, options) {
-                               if (options.containerSelector === '') {
-                                       throw new Error("[WoltLabSuite/Core/Ui/Reaction/CountButtons] Expected a non-empty string for option 'containerSelector'.");
-                               }
-                               
-                               this._containers = new Dictionary();
-                               this._objects = new Dictionary();
-                               this._objectType = objectType;
-                               
-                               this._options = Core.extend({
-                                       // selectors
-                                       summaryListSelector: '.reactionSummaryList',
-                                       containerSelector: '',
-                                       isSingleItem: false,
-                                       
-                                       // optional parameters
-                                       parameters: {
-                                               data: {}
-                                       }
-                               }, options);
-                               
-                               this.initContainers(options, objectType);
-                               
-                               DomChangeListener.add('WoltLabSuite/Core/Ui/Reaction/CountButtons-' + objectType, this.initContainers.bind(this));
-                       },
-                       
-                       /**
-                        * Initialises the containers. 
-                        */
-                       initContainers: function() {
-                               var element, elements = elBySelAll(this._options.containerSelector), elementData, triggerChange = false, objectId;
-                               for (var i = 0, length = elements.length; i < length; i++) {
-                                       element = elements[i];
-                                       if (this._containers.has(DomUtil.identify(element))) {
-                                               continue;
-                                       }
-                                       
-                                       elementData = {
-                                               reactButton: null,
-                                               summary: null,
-                                               
-                                               objectId: ~~elData(element, 'object-id'), 
-                                               element: element
-                                       };
-                                       
-                                       this._containers.set(DomUtil.identify(element), elementData);
-                                       this._initReactionCountButtons(element, elementData);
-                                       
-                                       if (!this._objects.has(~~elData(element, 'object-id'))) {
-                                               var objects = [];
-                                       }
-                                       else {
-                                               var objects = this._objects.get(~~elData(element, 'object-id'));
-                                       }
-                                       
-                                       objects.push(elementData);
-                                       
-                                       this._objects.set(~~elData(element, 'object-id'), objects);
-                                       
-                                       triggerChange = true;
-                               }
-                               
-                               if (triggerChange) {
-                                       DomChangeListener.trigger();
-                               }
-                       },
-                       
-                       /**
-                        * Update the count buttons with the given data. 
-                        * 
-                        * @param       {int}           objectId
-                        * @param       {object}        data
-                        */
-                       updateCountButtons: function(objectId, data) {
-                               var triggerChange = false;
-                               this._objects.get(objectId).forEach(function(elementData) {
-                                       var summaryList = elBySel(this._options.summaryListSelector, elementData.element);
-                                       
-                                       var sortedElements = {}, elements = elBySelAll('li', summaryList);
-                                       for (var i = 0, length = elements.length; i < length; i++) {
-                                               if (data[elData(elements[i], 'reaction-type-id')] !== undefined) {
-                                                       sortedElements[elData(elements[i], 'reaction-type-id')] = elements[i];
-                                               }
-                                               else {
-                                                       // reaction has no longer reactions
-                                                       elRemove(elements[i]);
-                                               }
-                                       }
-                                       
-                                       Object.keys(data).forEach(function(key) {
-                                               if (sortedElements[key] !== undefined) {
-                                                       var reactionCount = elBySel('.reactionCount', sortedElements[key]);
-                                                       reactionCount.innerHTML = StringUtil.shortUnit(data[key]);
-                                               }
-                                               else if (REACTION_TYPES[key] !== undefined) {
-                                                       // create element 
-                                                       var createdElement = elCreate('li');
-                                                       createdElement.className = 'reactCountButton';
-                                                       elData(createdElement, 'reaction-type-id', key);
-                                                       
-                                                       var countSpan = elCreate('span');
-                                                       countSpan.className = 'reactionCount';
-                                                       countSpan.innerHTML = StringUtil.shortUnit(data[key]);
-                                                       createdElement.appendChild(countSpan);
-                                                       
-                                                       createdElement.innerHTML = createdElement.innerHTML + REACTION_TYPES[key].renderedIcon;
-                                                       
-                                                       summaryList.appendChild(createdElement);
-                                                       
-                                                       this._initReactionCountButton(createdElement, objectId);
-                                                       
-                                                       triggerChange = true;
-                                               }
-                                       }, this);
-                               }.bind(this));
-                               
-                               if (triggerChange) {
-                                       DomChangeListener.trigger();
-                               }
-                       },
-                       
-                       /**
-                        * Initialized the reaction count buttons. 
-                        * 
-                        * @param       {element}        element
-                        * @param       {object}        elementData
-                        */
-                       _initReactionCountButtons: function(element, elementData) {
-                               if (this._options.isSingleItem) {
-                                       var summaryList = elBySel(this._options.summaryListSelector);
-                               }
-                               else {
-                                       var summaryList = elBySel(this._options.summaryListSelector, element);
-                               }
-                               
-                               if (summaryList !== null) {
-                                       var elements = elBySelAll('li', summaryList);
-                                       for (var i = 0, length = elements.length; i < length; i++) {
-                                               this._initReactionCountButton(elements[i], elementData.objectId);
-                                       }
-                               }
-                       },
-                       
-                       /**
-                        * Initialized a specific reaction count button for an object.
-                        *
-                        * @param       {element}        element
-                        * @param       {int}            objectId
-                        */
-                       _initReactionCountButton: function(element, objectId) {
-                               element.addEventListener(WCF_CLICK_EVENT, this._showReactionOverlay.bind(this, objectId));
-                       },
-                       
-                       /**
-                        * Shows the reaction overly for a specific object. 
-                        *
-                        * @param       {int}        objectId
-                        */
-                       _showReactionOverlay: function(objectId) {
-                               this._currentObjectId = objectId;
-                               this._showOverlay();
-                       },
-                       
-                       /**
-                        * Shows a specific page of the current opened reaction overlay. 
-                        *
-                        * @param       {int}        pageNo
-                        */
-                       _showOverlay: function() {
-                               this._options.parameters.data.containerID = this._objectType + '-' + this._currentObjectId;
-                               this._options.parameters.data.objectID = this._currentObjectId;
-                               this._options.parameters.data.objectType = this._objectType;
-                               
-                               Ajax.api(this, {
-                                       parameters: this._options.parameters
-                               });
-                       },
-                       
-                       _ajaxSuccess: function(data) {
-                               UiDialog.open(this, data.returnValues.template);
-                               UiDialog.setTitle('userReactionOverlay-' + this._objectType, data.returnValues.title);
-                       },
-                       
-                       _ajaxSetup: function() {
-                               return {
-                                       data: {
-                                               actionName: 'getReactionDetails',
-                                               className: '\\wcf\\data\\reaction\\ReactionAction'
-                                       }
-                               };
-                       },
-                       
-                       _dialogSetup: function() {
-                               return {
-                                       id: 'userReactionOverlay-' + this._objectType,
-                                       options: {
-                                               title: ""
-                                       },
-                                       source: null
-                               };
-                       }
-               };
-               
-               return CountButtons;
-       });
-
-/**
- * Provides interface elements to use reactions.
- *
- * @author     Joshua Ruesweg
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Reaction/Handler
- * @since       5.2
- */
-define(
-       'WoltLabSuite/Core/Ui/Reaction/Handler',[
-               'Ajax',      'Core',                            'Dictionary',           'Language',
-               'ObjectMap', 'StringUtil',                      'Dom/ChangeListener',   'Dom/Util',
-               'Ui/Dialog', 'WoltLabSuite/Core/Ui/User/List',  'User',                 'WoltLabSuite/Core/Ui/Reaction/CountButtons',
-               'Ui/Alignment', 'Ui/CloseOverlay',              'Ui/Screen'
-       ],
-       function(
-               Ajax,        Core,              Dictionary,             Language,
-               ObjectMap,   StringUtil,        DomChangeListener,      DomUtil,
-               UiDialog,    UiUserList,        User,                   CountButtons,
-               UiAlignment, UiCloseOverlay,    UiScreen
-       )
-       {
-               "use strict";
-               
-               /**
-                * @constructor
-                */
-               function UiReactionHandler(objectType, options) { this.init(objectType, options); }
-               UiReactionHandler.prototype = {
-                       /**
-                        * Initializes the reaction handler.
-                        * 
-                        * @param       {string}        objectType      object type
-                        * @param       {object}        options         initialization options
-                        */
-                       init: function(objectType, options) {
-                               if (options.containerSelector === '') {
-                                       throw new Error("[WoltLabSuite/Core/Ui/Reaction/Handler] Expected a non-empty string for option 'containerSelector'.");
-                               }
-                               
-                               this._containers = new Dictionary();
-                               this._details = new ObjectMap();
-                               this._objectType = objectType;
-                               this._cache = new Dictionary();
-                               this._objects = new Dictionary();
-                               
-                               this._popoverCurrentObjectId = 0;
-                               
-                               this._popover = null;
-                               
-                               this._options = Core.extend({
-                                       // selectors
-                                       buttonSelector: '.reactButton',
-                                       containerSelector: '',
-                                       isButtonGroupNavigation: false,
-                                       isSingleItem: false,
-                                       
-                                       // other stuff
-                                       parameters: {
-                                               data: {}
-                                       }
-                               }, options);
-                               
-                               this.initReactButtons(options, objectType);
-                               
-                               this.countButtons = new CountButtons(this._objectType, this._options);
-                               
-                               DomChangeListener.add('WoltLabSuite/Core/Ui/Reaction/Handler-' + objectType, this.initReactButtons.bind(this));
-                               UiCloseOverlay.add('WoltLabSuite/Core/Ui/Reaction/Handler', this._closePopover.bind(this));
-                       },
-                       
-                       /**
-                        * Initializes all applicable react buttons with the given selector.
-                        */
-                       initReactButtons: function() {
-                               var element, elements = elBySelAll(this._options.containerSelector), elementData, triggerChange = false, objectId;
-                               for (var i = 0, length = elements.length; i < length; i++) {
-                                       element = elements[i];
-                                       if (this._containers.has(DomUtil.identify(element))) {
-                                               continue;
-                                       }
-                                       
-                                       elementData = {
-                                               reactButton: null,
-                                               objectId: ~~elData(element, 'object-id'),
-                                               element: element
-                                       };
-                                       
-                                       this._containers.set(DomUtil.identify(element), elementData);
-                                       this._initReactButton(element, elementData);
-                                       
-                                       if (!this._objects.has(~~elData(element, 'object-id'))) {
-                                               var objects = [];
-                                       }
-                                       else {
-                                               var objects = this._objects.get(~~elData(element, 'object-id'));
-                                       }
-                                       
-                                       objects.push(elementData);
-                                       
-                                       this._objects.set(~~elData(element, 'object-id'), objects);
-                                       
-                                       triggerChange = true;
-                               }
-                               
-                               if (triggerChange) {
-                                       DomChangeListener.trigger();
-                               }
-                       },
-                       
-                       
-                       /**
-                        * Initializes a specific react button.
-                        */
-                       _initReactButton: function(element, elementData) {
-                               if (this._options.isSingleItem) {
-                                       elementData.reactButton = elBySel(this._options.buttonSelector);
-                               }
-                               else {
-                                       elementData.reactButton = elBySel(this._options.buttonSelector, element);
-                               }
-                               
-                               if (elementData.reactButton === null || elementData.reactButton.length === 0) {
-                                       // the element may have no react button 
-                                       return;
-                               }
-                               
-                               if (Object.keys(REACTION_TYPES).length === 1) {
-                                       var reaction = REACTION_TYPES[Object.keys(REACTION_TYPES)[0]];
-                                       elementData.reactButton.title = reaction.title;
-                                       var textSpan = elBySel('.invisible', elementData.reactButton);
-                                       textSpan.innerText = reaction.title;
-                               }
-                               
-                               if (elementData.reactButton.closest('.messageFooterGroup > .jsMobileNavigation')) {
-                                       UiScreen.on('screen-sm-down', {
-                                               match: this._enableMobileView.bind(this, elementData.reactButton, elementData.objectId),
-                                               unmatch: this._disableMobileView.bind(this, elementData.reactButton, elementData.objectId),
-                                               setup: this._setupMobileView.bind(this, elementData.reactButton, elementData.objectId)
-                                       });
-                               }
-                               
-                               elementData.reactButton.addEventListener(WCF_CLICK_EVENT, this._toggleReactPopover.bind(this, elementData.objectId, elementData.reactButton));
-                       },
-                       
-                       /**
-                        * Enables the mobile view for the reaction button.
-                        * 
-                        * @param       {Element}       element
-                        */
-                       _enableMobileView: function(element) {
-                               var messageFooterGroup = element.closest('.messageFooterGroup');
-                               
-                               elShow(elBySel('.mobileReactButton', messageFooterGroup));
-                       },
-                       
-                       /**
-                        * Disables the mobile view for the reaction button.
-                        * 
-                        * @param       {Element}       element
-                        */
-                       _disableMobileView: function(element) {
-                               var messageFooterGroup = element.closest('.messageFooterGroup');
-                               
-                               elHide(elBySel('.mobileReactButton', messageFooterGroup));
-                       },
-                       
-                       /**
-                        * Setup the mobile view for the reaction button.
-                        * 
-                        * @param       {Element}       element
-                        * @param       {int}           objectID
-                        */
-                       _setupMobileView: function(element, objectID) {
-                               var messageFooterGroup = element.closest('.messageFooterGroup');
-                               
-                               var button = elCreate('button');
-                               button.classList = 'mobileReactButton';
-                               button.innerHTML = element.innerHTML;
-                               
-                               button.addEventListener(WCF_CLICK_EVENT, this._toggleReactPopover.bind(this, objectID, button));
-                               
-                               messageFooterGroup.appendChild(button);
-                       },
-                       
-                       _updateReactButton: function(objectID, reactionTypeID) {
-                               this._objects.get(objectID).forEach(function (elementData) {
-                                       if (reactionTypeID) {
-                                               elementData.reactButton.classList.add('active');
-                                               elData(elementData.reactButton, 'reaction-type-id', reactionTypeID);
-                                       }
-                                       else {
-                                               elData(elementData.reactButton, 'reaction-type-id', 0);
-                                               elementData.reactButton.classList.remove('active');
-                                       }
-                               });
-                       },
-                       
-                       _markReactionAsActive: function() {
-                               var reactionTypeID = elData(this._objects.get(this._popoverCurrentObjectId)[0].reactButton, 'reaction-type-id');
-                               
-                               //  clear old active state
-                               var elements = elBySelAll('.reactionTypeButton.active', this._getPopover());
-                               for (var i = 0, length = elements.length; i < length; i++) {
-                                       elements[i].classList.remove('active');
-                               }
-                               
-                               if (reactionTypeID != 0) {
-                                       elBySel('.reactionTypeButton[data-reaction-type-id="'+reactionTypeID+'"]', this._getPopover()).classList.add('active');
-                               }
-                       },
-                       
-                       /**
-                        * Toggle the visibility of the react popover.
-                        * 
-                        * @param       {int}           objectId
-                        * @param       {Element}       element
-                        */
-                       _toggleReactPopover: function(objectId, element, event) {
-                               if (event !== null) {
-                                       event.preventDefault();
-                                       event.stopPropagation();
-                               }
-                               
-                               if (Object.keys(REACTION_TYPES).length === 1) {
-                                       var reaction = REACTION_TYPES[Object.keys(REACTION_TYPES)[0]];
-                                       this._popoverCurrentObjectId = objectId;
-                                       
-                                       this._react(reaction.reactionTypeID);
-                               }
-                               else {
-                                       if (this._popoverCurrentObjectId === 0 || this._popoverCurrentObjectId !== objectId) {
-                                               this._openReactPopover(objectId, element);
-                                       }
-                                       else {
-                                               this._closePopover(objectId, element);
-                                       }
-                               }
-                       },
-                       
-                       /**
-                        * Opens the react popover for a specific react button.
-                        * 
-                        * @param       {int}           objectId                objectId of the element
-                        * @param       {Element}       element                 container element
-                        */
-                       _openReactPopover: function(objectId, element) {
-                               // first close old popover, if exists 
-                               if (this._popoverCurrentObjectId !== 0) {
-                                       this._closePopover();
-                               }
-                               
-                               this._popoverCurrentObjectId = objectId;
-                               this._markReactionAsActive();
-                               
-                               UiAlignment.set(this._getPopover(), element, {
-                                       pointer: true,
-                                       horizontal: (this._options.isButtonGroupNavigation) ? 'left' :'center',
-                                       vertical: 'top'
-                               });
-                               
-                               if (this._options.isButtonGroupNavigation) {
-                                       // find nav element
-                                       var nav = element.closest('nav');
-                                       nav.style.opacity = "1";
-                               }
-                               
-                               this._getPopover().classList.remove('forceHide');
-                               this._getPopover().classList.add('active');
-                       },
-                       
-                       /**
-                        * Returns the react popover element.
-                        * 
-                        * @returns {Element}
-                        */
-                       _getPopover: function() {
-                               if (this._popover == null) {
-                                       this._popover = elCreate('div');
-                                       this._popover.className = 'reactionPopover forceHide';
-                                       
-                                       var _popoverContent = elCreate('div');
-                                       _popoverContent.className = 'reactionPopoverContent';
-                                       
-                                       var popoverContentHTML = elCreate('ul');
-                                       
-                                       var sortedReactionTypes = this._getSortedReactionTypes();
-                                       
-                                       for (var key in sortedReactionTypes) {
-                                               if (!sortedReactionTypes.hasOwnProperty(key)) continue;
-                                               
-                                               var reactionType = sortedReactionTypes[key];
-                                               
-                                               var reactionTypeItem = elCreate('li');
-                                               reactionTypeItem.className = 'reactionTypeButton jsTooltip';
-                                               elData(reactionTypeItem, 'reaction-type-id', reactionType.reactionTypeID);
-                                               elData(reactionTypeItem, 'title', reactionType.title);
-                                               reactionTypeItem.title = reactionType.title;
-                                               
-                                               var reactionTypeItemSpan = elCreate('span');
-                                               reactionTypeItemSpan.classList = 'reactionTypeButtonTitle';
-                                               reactionTypeItemSpan.innerHTML = reactionType.title;
-                                               
-                                               reactionTypeItem.innerHTML = reactionType.renderedIcon;
-                                               
-                                               reactionTypeItem.appendChild(reactionTypeItemSpan);
-                                               
-                                               reactionTypeItem.addEventListener(WCF_CLICK_EVENT, this._react.bind(this, reactionType.reactionTypeID));
-                                               
-                                               popoverContentHTML.appendChild(reactionTypeItem);
-                                       }
-                                       
-                                       _popoverContent.appendChild(popoverContentHTML);
-                                       this._popover.appendChild(_popoverContent);
-                                       
-                                       var pointer = elCreate('span');
-                                       pointer.className = 'elementPointer';
-                                       pointer.appendChild(elCreate('span'));
-                                       this._popover.appendChild(pointer);
-                                       
-                                       document.body.appendChild(this._popover);
-                                       
-                                       DomChangeListener.trigger();
-                               }
-                               
-                               return this._popover;
-                       },
-                       
-                       /**
-                        * Sort the reaction types by the showOrder field.
-                        * 
-                        * @returns     {Array}         the reaction types sorted by showOrder
-                        */
-                       _getSortedReactionTypes: function() {
-                               var sortedReactionTypes = [];
-                               
-                               // convert our reaction type object to an array
-                               for (var key in REACTION_TYPES) {
-                                       if (!REACTION_TYPES.hasOwnProperty(key)) continue;
-                                       sortedReactionTypes.push(REACTION_TYPES[key]);
-                               }
-                               
-                               // sort the array
-                               sortedReactionTypes.sort(function (a, b) {
-                                       return a.showOrder - b.showOrder;
-                               });
-                               
-                               return sortedReactionTypes;
-                       },
-                       
-                       /**
-                        * Closes the react popover.
-                        */
-                       _closePopover: function() {
-                               if (this._popoverCurrentObjectId !== 0) {
-                                       this._getPopover().classList.remove('active');
-                                       
-                                       if (this._options.isButtonGroupNavigation) {
-                                               this._objects.get(this._popoverCurrentObjectId).forEach(function (elementData) {
-                                                       elementData.reactButton.closest('nav').style.cssText = "";
-                                               });
-                                       }
-                                       
-                                       this._popoverCurrentObjectId = 0;
-                               }
-                       },
-                       
-                       /**
-                        * React with the given reactionTypeId on an object.
-                        * 
-                        * @param       {init}          reactionTypeId
-                        */
-                       _react: function(reactionTypeId) {
-                               this._options.parameters.reactionTypeID = reactionTypeId;
-                               this._options.parameters.data.containerID = this._currentReactionTypeId;
-                               this._options.parameters.data.objectID = this._popoverCurrentObjectId;
-                               this._options.parameters.data.objectType = this._objectType;
-                               
-                               Ajax.api(this, {
-                                       parameters: this._options.parameters
-                               });
-                               
-                               this._closePopover();
-                       },
-                       
-                       _ajaxSuccess: function(data) {
-                               this.countButtons.updateCountButtons(data.returnValues.objectID, data.returnValues.reactions);
-                               
-                               // update react button status
-                               this._updateReactButton(data.returnValues.objectID, data.returnValues.reactionTypeID);
-                       },
-                       
-                       _ajaxSetup: function() {
-                               return {
-                                       data: {
-                                               actionName: 'react',
-                                               className: '\\wcf\\data\\reaction\\ReactionAction'
-                                       }
-                               };
-                       }
-               };
-               
-               return UiReactionHandler;
-       });
-
-/**
- * Provides interface elements to display and review likes.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Like/Handler
- * @deprecated  5.2 use ReactionHandler instead 
- */
-define(
-       'WoltLabSuite/Core/Ui/Like/Handler',[
-               'Ajax',      'Core',                     'Dictionary',         'Language',
-               'ObjectMap', 'StringUtil',               'Dom/ChangeListener', 'Dom/Util',
-               'Ui/Dialog', 'WoltLabSuite/Core/Ui/User/List', 'User',         'WoltLabSuite/Core/Ui/Reaction/Handler'
-       ],
-       function(
-               Ajax,        Core,                        Dictionary,           Language,
-               ObjectMap,   StringUtil,                  DomChangeListener,    DomUtil,
-               UiDialog,    UiUserList,                  User,                 UiReactionHandler
-       )
-{
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function UiLikeHandler(objectType, options) { this.init(objectType, options); }
-       UiLikeHandler.prototype = {
-               /**
-                * Initializes the like handler.
-                * 
-                * @param       {string}        objectType      object type
-                * @param       {object}        options         initialization options
-                */
-               init: function(objectType, options) {
-                       if (options.containerSelector === '') {
-                               throw new Error("[WoltLabSuite/Core/Ui/Like/Handler] Expected a non-empty string for option 'containerSelector'.");
-                       }
-                       
-                       this._containers = new ObjectMap();
-                       this._details = new ObjectMap();
-                       this._objectType = objectType;
-                       this._options = Core.extend({
-                               // settings
-                               badgeClassNames: '',
-                               isSingleItem: false,
-                               markListItemAsActive: false,
-                               renderAsButton: true,
-                               summaryPrepend: true,
-                               summaryUseIcon: true,
-                               
-                               // permissions
-                               canDislike: false,
-                               canLike: false,
-                               canLikeOwnContent: false,
-                               canViewSummary: false,
-                               
-                               // selectors
-                               badgeContainerSelector: '.messageHeader .messageStatus',
-                               buttonAppendToSelector: '.messageFooter .messageFooterButtons',
-                               buttonBeforeSelector: '',
-                               containerSelector: '',
-                               summarySelector: '.messageFooterGroup'
-                       }, options);
-                       
-                       this.initContainers(options, objectType);
-                       
-                       DomChangeListener.add('WoltLabSuite/Core/Ui/Like/Handler-' + objectType, this.initContainers.bind(this));
-                       
-                       new UiReactionHandler(this._objectType, {
-                               containerSelector: this._options.containerSelector,
-                               summaryListSelector: '.reactionSummaryList'
-                       });
-               },
-               
-               /**
-                * Initializes all applicable containers.
-                */
-               initContainers: function() {
-                       var element, elements = elBySelAll(this._options.containerSelector), elementData, triggerChange = false;
-                       for (var i = 0, length = elements.length; i < length; i++) {
-                               element = elements[i];
-                               if (this._containers.has(element)) {
-                                       continue;
-                               }
-                               
-                               elementData = {
-                                       badge: null,
-                                       dislikeButton: null,
-                                       likeButton: null,
-                                       summary: null,
-                                       
-                                       dislikes: ~~elData(element, 'like-dislikes'),
-                                       liked: ~~elData(element, 'like-liked'),
-                                       likes: ~~elData(element, 'like-likes'),
-                                       objectId: ~~elData(element, 'object-id'),
-                                       users: JSON.parse(elData(element, 'like-users'))
-                               };
-                               
-                               this._containers.set(element, elementData);
-                               this._buildWidget(element, elementData);
-                               
-                               triggerChange = true;
-                       }
-                       
-                       if (triggerChange) {
-                               DomChangeListener.trigger();
-                       }
-               },
-               
-               /**
-                * Creates the interface elements.
-                * 
-                * @param       {Element}       element         container element
-                * @param       {object}        elementData     like data
-                */
-               _buildWidget: function(element, elementData) {
-                       // build reaction summary list
-                       var summaryList, listItem, badgeContainer, isSummaryPosition = true;
-                       badgeContainer = (this._options.isSingleItem) ? elBySel(this._options.summarySelector) : elBySel(this._options.summarySelector, element);
-                       if (badgeContainer === null) {
-                               badgeContainer = (this._options.isSingleItem) ? elBySel(this._options.badgeContainerSelector) : elBySel(this._options.badgeContainerSelector, element);
-                               isSummaryPosition = false;
-                       }
-                       
-                       if (badgeContainer !== null) {
-                               summaryList = elCreate('ul');
-                               summaryList.classList.add('reactionSummaryList');
-                               if (isSummaryPosition) {
-                                       summaryList.classList.add('likesSummary');
-                               }
-                               else {
-                                       summaryList.classList.add('reactionSummaryListTiny');
-                               }
-                               
-                               for (var key in elementData.users) {
-                                       if (key === "reactionTypeID") continue;
-                                       if (!REACTION_TYPES.hasOwnProperty(key)) continue;
-                                       
-                                       // create element 
-                                       var createdElement = elCreate('li');
-                                       createdElement.className = 'reactCountButton';
-                                       elData(createdElement, 'reaction-type-id', key);
-                                       
-                                       var countSpan = elCreate('span');
-                                       countSpan.className = 'reactionCount';
-                                       countSpan.innerHTML = StringUtil.shortUnit(elementData.users[key]);
-                                       createdElement.appendChild(countSpan);
-                                       
-                                       createdElement.innerHTML = createdElement.innerHTML + REACTION_TYPES[key].renderedIcon;
-                                       
-                                       summaryList.appendChild(createdElement);
-                                       
-                               }
-                               
-                               if (isSummaryPosition) {
-                                       if (this._options.summaryPrepend) {
-                                               DomUtil.prepend(summaryList, badgeContainer);
-                                       }
-                                       else {
-                                               badgeContainer.appendChild(summaryList);
-                                       }
-                               }
-                               else {
-                                       if (badgeContainer.nodeName === 'OL' || badgeContainer.nodeName === 'UL') {
-                                               listItem = elCreate('li');
-                                               listItem.appendChild(summaryList);
-                                               badgeContainer.appendChild(listItem);
-                                       }
-                                       else {
-                                               badgeContainer.appendChild(summaryList);
-                                       }
-                               }
-                               
-                               elementData.badge = summaryList;
-                       }
-                       
-                       // build reaction button
-                       if (this._options.canLike && (User.userId != elData(element, 'user-id') || this._options.canLikeOwnContent)) {
-                               var appendTo = (this._options.buttonAppendToSelector) ? ((this._options.isSingleItem) ? elBySel(this._options.buttonAppendToSelector) : elBySel(this._options.buttonAppendToSelector, element)) : null;
-                               var insertPosition = (this._options.buttonBeforeSelector) ? ((this._options.isSingleItem) ? elBySel(this._options.buttonBeforeSelector) : elBySel(this._options.buttonBeforeSelector, element)) : null;
-                               if (insertPosition === null && appendTo === null) {
-                                       throw new Error("Unable to find insert location for like/dislike buttons.");
-                               }
-                               else {
-                                       elementData.likeButton = this._createButton(element, elementData.users.reactionTypeID, insertPosition, appendTo);
-                               }
-                       }
-               },
-               
-               /**
-                * Creates a reaction button.
-                * 
-                * @param       {Element}       element                 container element
-                * @param       {int}           reactionTypeID          the reactionTypeID of the current state
-                * @param       {Element?}      insertBefore            insert button before given element
-                * @param       {Element?}      appendTo                append button to given element
-                * @return      {Element}       button element 
-                */
-               _createButton: function(element, reactionTypeID, insertBefore, appendTo) {
-                       var title = Language.get('wcf.reactions.react');
-                       
-                       var listItem = elCreate('li');
-                       listItem.className = 'wcfReactButton';
-                       
-                       if (insertBefore) {
-                               var jsMobileNavigation = insertBefore.parentElement.contains('jsMobileNavigation');
-                       }
-                       else {
-                               var jsMobileNavigation = appendTo.classList.contains('jsMobileNavigation');
-                       }
-                       
-                       var button = elCreate('a');
-                       button.className = 'jsTooltip reactButton';
-                       if (this._options.renderAsButton) {
-                               button.classList.add('button');
-                               
-                               if (jsMobileNavigation) {
-                                       button.classList.add('ignoreMobileNavigation');
-                               }
-                       }
-                       
-                       button.href = '#';
-                       button.title = title;
-                       
-                       var icon = elCreate('span');
-                       icon.className = 'icon icon16 fa-smile-o';
-                       
-                       if (reactionTypeID === undefined || reactionTypeID == 0) {
-                               elData(icon, 'reaction-type-id', 0);
-                       }
-                       else {
-                               elData(button, 'reaction-type-id', reactionTypeID);
-                               button.classList.add("active");
-                       }
-                       
-                       button.appendChild(icon);
-                       
-                       var invisibleText = elCreate("span");
-                       invisibleText.className = "invisible";
-                       invisibleText.innerHTML = title;
-                       
-                       button.appendChild(document.createTextNode(" "));
-                       button.appendChild(invisibleText);
-                       
-                       listItem.appendChild(button);
-                       
-                       if (insertBefore) {
-                               insertBefore.parentNode.insertBefore(listItem, insertBefore);
-                       }
-                       else {
-                               appendTo.appendChild(listItem);
-                       }
-                       
-                       return button;
-               }
-       };
-       
-       return UiLikeHandler;
-});
-
-/**
- * Flexible message inline editor.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Message/InlineEditor
- */
-define(
-       'WoltLabSuite/Core/Ui/Message/InlineEditor',[
-               'Ajax',         'Core',            'Dictionary',          'Environment',
-               'EventHandler', 'Language',        'ObjectMap',           'Dom/ChangeListener', 'Dom/Traverse',
-               'Dom/Util',     'Ui/Notification', 'Ui/ReusableDropdown', 'WoltLabSuite/Core/Ui/Scroll'
-       ],
-       function(
-               Ajax,            Core,              Dictionary,            Environment,
-               EventHandler,    Language,          ObjectMap,             DomChangeListener,    DomTraverse,
-               DomUtil,         UiNotification,    UiReusableDropdown,    UiScroll
-       )
-{
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       rebuild: function() {},
-                       _click: function() {},
-                       _clickDropdown: function() {},
-                       _dropdownBuild: function() {},
-                       _dropdownToggle: function() {},
-                       _dropdownGetItems: function() {},
-                       _dropdownOpen: function() {},
-                       _dropdownSelect: function() {},
-                       _clickDropdownItem: function() {},
-                       _prepare: function() {},
-                       _showEditor: function() {},
-                       _restoreMessage: function() {},
-                       _save: function() {},
-                       _validate: function() {},
-                       throwError: function() {},
-                       _showMessage: function() {},
-                       _hideEditor: function() {},
-                       _restoreEditor: function() {},
-                       _destroyEditor: function() {},
-                       _getHash: function() {},
-                       _updateHistory: function() {},
-                       _getEditorId: function() {},
-                       _getObjectId: function() {},
-                       _ajaxFailure: function() {},
-                       _ajaxSuccess: function() {},
-                       _ajaxSetup: function() {},
-                       legacyEdit: function() {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * @constructor
-        */
-       function UiMessageInlineEditor(options) { this.init(options); }
-       UiMessageInlineEditor.prototype = {
-               /**
-                * Initializes the message inline editor.
-                * 
-                * @param       {Object}        options         list of configuration options
-                */
-               init: function(options) {
-                       this._activeDropdownElement = null;
-                       this._activeElement = null;
-                       this._dropdownMenu = null;
-                       this._elements = new ObjectMap();
-                       this._options = Core.extend({
-                               canEditInline: false,
-                               
-                               className: '',
-                               containerId: 0,
-                               dropdownIdentifier: '',
-                               editorPrefix: 'messageEditor',
-                               
-                               messageSelector: '.jsMessage',
-                               
-                               quoteManager: null
-                       }, options);
-                       
-                       this.rebuild();
-                       
-                       DomChangeListener.add('Ui/Message/InlineEdit_' + this._options.className, this.rebuild.bind(this));
-               },
-               
-               /**
-                * Initializes each applicable message, should be called whenever new
-                * messages are being displayed.
-                */
-               rebuild: function() {
-                       var button, canEdit, element, elements = elBySelAll(this._options.messageSelector);
-                       
-                       for (var i = 0, length = elements.length; i < length; i++) {
-                               element = elements[i];
-                               if (this._elements.has(element)) {
-                                       continue;
-                               }
-                               
-                               button = elBySel('.jsMessageEditButton', element);
-                               if (button !== null) {
-                                       canEdit = elDataBool(element, 'can-edit');
-                                       
-                                       if (this._options.canEditInline || elDataBool(element, 'can-edit-inline')) {
-                                               button.addEventListener(WCF_CLICK_EVENT, this._clickDropdown.bind(this, element));
-                                               button.classList.add('jsDropdownEnabled');
-                                               
-                                               if (canEdit) {
-                                                       button.addEventListener('dblclick', this._click.bind(this, element));
-                                               }
-                                       }
-                                       else if (canEdit) {
-                                               button.addEventListener(WCF_CLICK_EVENT, this._click.bind(this, element));
-                                       }
-                               }
-                               
-                               var messageBody = elBySel('.messageBody', element);
-                               var messageFooter = elBySel('.messageFooter', element);
-                               var messageHeader = elBySel('.messageHeader', element);
-                               
-                               this._elements.set(element, {
-                                       button: button,
-                                       messageBody: messageBody,
-                                       messageBodyEditor: null,
-                                       messageFooter: messageFooter,
-                                       messageFooterButtons: elBySel('.messageFooterButtons', messageFooter),
-                                       messageHeader: messageHeader,
-                                       messageText: elBySel('.messageText', messageBody)
-                               });
-                       }
-               },
-               
-               /**
-                * Handles clicks on the edit button or the edit dropdown item.
-                * 
-                * @param       {Element}       element         message element
-                * @param       {?Event}        event           event object
-                * @protected
-                */
-               _click: function(element, event) {
-                       if (element === null) element = this._activeDropdownElement;
-                       if (event) event.preventDefault();
-                       
-                       if (this._activeElement === null) {
-                               this._activeElement = element;
-                               
-                               this._prepare();
-                               
-                               Ajax.api(this, {
-                                       actionName: 'beginEdit',
-                                       parameters: {
-                                               containerID: this._options.containerId,
-                                               objectID: this._getObjectId(element)
-                                       }
-                               });
-                       }
-                       else {
-                               UiNotification.show('wcf.message.error.editorAlreadyInUse', null, 'warning');
-                       }
-               },
-               
-               /**
-                * Creates and opens the dropdown on first usage.
-                * 
-                * @param       {Element}       element         message element
-                * @param       {Object}        event           event object
-                * @protected
-                */
-               _clickDropdown: function(element, event) {
-                       event.preventDefault();
-                       
-                       var button = event.currentTarget;
-                       if (button.classList.contains('dropdownToggle')) {
-                               return;
-                       }
-                       
-                       button.classList.add('dropdownToggle');
-                       button.parentNode.classList.add('dropdown');
-                       (function(button, element) {
-                               button.addEventListener(WCF_CLICK_EVENT, (function(event) {
-                                       event.preventDefault();
-                                       event.stopPropagation();
-                                       
-                                       this._activeDropdownElement = element;
-                                       UiReusableDropdown.toggleDropdown(this._options.dropdownIdentifier, button);
-                               }).bind(this));
-                       }).bind(this)(button, element);
-                       
-                       // build dropdown
-                       if (this._dropdownMenu === null) {
-                               this._dropdownMenu = elCreate('ul');
-                               this._dropdownMenu.className = 'dropdownMenu';
-                               
-                               var items = this._dropdownGetItems();
-                               
-                               EventHandler.fire('com.woltlab.wcf.inlineEditor', 'dropdownInit_' + this._options.dropdownIdentifier, {
-                                       items: items
-                               });
-                               
-                               this._dropdownBuild(items);
-                               
-                               UiReusableDropdown.init(this._options.dropdownIdentifier, this._dropdownMenu);
-                               UiReusableDropdown.registerCallback(this._options.dropdownIdentifier, this._dropdownToggle.bind(this));
-                       }
-                       
-                       setTimeout(function() {
-                               Core.triggerEvent(button, WCF_CLICK_EVENT);
-                       }, 10);
-               },
-               
-               /**
-                * Creates the dropdown menu on first usage.
-                * 
-                * @param       {Object}        items   list of dropdown items
-                * @protected
-                */
-               _dropdownBuild: function(items) {
-                       var item, label, listItem;
-                       var callbackClick = this._clickDropdownItem.bind(this);
-                       
-                       for (var i = 0, length = items.length; i < length; i++) {
-                               item = items[i];
-                               listItem = elCreate('li');
-                               elData(listItem, 'item', item.item);
-                               
-                               if (item.item === 'divider') {
-                                       listItem.className = 'dropdownDivider';
-                               }
-                               else {
-                                       label = elCreate('span');
-                                       label.textContent = Language.get(item.label);
-                                       listItem.appendChild(label);
-                                       
-                                       if (item.item === 'editItem') {
-                                               listItem.addEventListener(WCF_CLICK_EVENT, this._click.bind(this, null));
-                                       }
-                                       else {
-                                               listItem.addEventListener(WCF_CLICK_EVENT, callbackClick);
-                                       }
-                               }
-                               
-                               this._dropdownMenu.appendChild(listItem);
-                       }
-               },
-               
-               /**
-                * Callback for dropdown toggle.
-                * 
-                * @param       {int}           containerId     container id
-                * @param       {string}        action          toggle action, either 'open' or 'close'
-                * @protected
-                */
-               _dropdownToggle: function(containerId, action) {
-                       var elementData = this._elements.get(this._activeDropdownElement);
-                       elementData.button.parentNode.classList[(action === 'open' ? 'add' : 'remove')]('dropdownOpen');
-                       elementData.messageFooterButtons.classList[(action === 'open' ? 'add' : 'remove')]('forceVisible');
-                       
-                       if (action === 'open') {
-                               var visibility = this._dropdownOpen();
-                               
-                               EventHandler.fire('com.woltlab.wcf.inlineEditor', 'dropdownOpen_' + this._options.dropdownIdentifier, {
-                                       element: this._activeDropdownElement,
-                                       visibility: visibility
-                               });
-                               
-                               var item, listItem, visiblePredecessor = false;
-                               for (var i = 0; i < this._dropdownMenu.childElementCount; i++) {
-                                       listItem = this._dropdownMenu.children[i];
-                                       item = elData(listItem, 'item');
-                                       
-                                       if (item === 'divider') {
-                                               if (visiblePredecessor) {
-                                                       elShow(listItem);
-                                                       
-                                                       visiblePredecessor = false;
-                                               }
-                                               else {
-                                                       elHide(listItem);
-                                               }
-                                       }
-                                       else {
-                                               if (objOwns(visibility, item) && visibility[item] === false) {
-                                                       elHide(listItem);
-                                                       
-                                                       // check if previous item was a divider
-                                                       if (i > 0 && i + 1 === this._dropdownMenu.childElementCount) {
-                                                               if (elData(listItem.previousElementSibling, 'item') === 'divider') {
-                                                                       elHide(listItem.previousElementSibling);
-                                                               }
-                                                       }
-                                               }
-                                               else {
-                                                       elShow(listItem);
-                                                       
-                                                       visiblePredecessor = true;
-                                               }
-                                       }
-                               }
-                       }
-               },
-               
-               /**
-                * Returns the list of dropdown items for this type.
-                * 
-                * @return      {Array<Object>}         list of objects containing the type name and label
-                * @protected
-                */
-               _dropdownGetItems: function() {},
-               
-               /**
-                * Invoked once the dropdown for this type is shown, expects a list of type name and a boolean value
-                * to represent the visibility of each item. Items that do not appear in this list will be considered
-                * visible.
-                * 
-                * @return      {Object<string, boolean>}
-                * @protected
-                */
-               _dropdownOpen: function() {},
-               
-               /**
-                * Invoked whenever the user selects an item from the dropdown menu, the selected item is passed as argument.
-                * 
-                * @param       {string}        item    selected dropdown item
-                * @protected
-                */
-               _dropdownSelect: function(item) {},
-               
-               /**
-                * Handles clicks on a dropdown item.
-                * 
-                * @param       {Event}         event   event object
-                * @protected
-                */
-               _clickDropdownItem: function(event) {
-                       event.preventDefault();
-                       
-                       //noinspection JSCheckFunctionSignatures
-                       var item = elData(event.currentTarget, 'item');
-                       var data = {
-                               cancel: false,
-                               element: this._activeDropdownElement,
-                               item: item
-                       };
-                       EventHandler.fire('com.woltlab.wcf.inlineEditor', 'dropdownItemClick_' + this._options.dropdownIdentifier, data);
-                       
-                       if (data.cancel === true) {
-                               event.preventDefault();
-                       }
-                       else {
-                               this._dropdownSelect(item);
-                       }
-               },
-               
-               /**
-                * Prepares the message for editor display.
-                * 
-                * @protected
-                */
-               _prepare: function() {
-                       var data = this._elements.get(this._activeElement);
-                       
-                       var messageBodyEditor = elCreate('div');
-                       messageBodyEditor.className = 'messageBody editor';
-                       data.messageBodyEditor = messageBodyEditor;
-                       
-                       var icon = elCreate('span');
-                       icon.className = 'icon icon48 fa-spinner';
-                       messageBodyEditor.appendChild(icon);
-                       
-                       DomUtil.insertAfter(messageBodyEditor, data.messageBody);
-                       
-                       elHide(data.messageBody);
-               },
-               
-               /**
-                * Shows the message editor.
-                * 
-                * @param       {Object}        data            ajax response data
-                * @protected
-                */
-               _showEditor: function(data) {
-                       var id = this._getEditorId();
-                       var elementData = this._elements.get(this._activeElement);
-                       
-                       this._activeElement.classList.add('jsInvalidQuoteTarget');
-                       var icon = DomTraverse.childByClass(elementData.messageBodyEditor, 'icon');
-                       elRemove(icon);
-                       
-                       var messageBody = elementData.messageBodyEditor;
-                       var editor = elCreate('div');
-                       editor.className = 'editorContainer';
-                       //noinspection JSUnresolvedVariable
-                       DomUtil.setInnerHtml(editor, data.returnValues.template);
-                       messageBody.appendChild(editor);
-                       
-                       // bind buttons
-                       var formSubmit = elBySel('.formSubmit', editor);
-                       
-                       var buttonSave = elBySel('button[data-type="save"]', formSubmit);
-                       buttonSave.addEventListener(WCF_CLICK_EVENT, this._save.bind(this));
-                       
-                       var buttonCancel = elBySel('button[data-type="cancel"]', formSubmit);
-                       buttonCancel.addEventListener(WCF_CLICK_EVENT, this._restoreMessage.bind(this));
-                       
-                       EventHandler.add('com.woltlab.wcf.redactor', 'submitEditor_' + id, (function(data) {
-                               data.cancel = true;
-                               
-                               this._save();
-                       }).bind(this));
-                       
-                       // hide message header and footer
-                       elHide(elementData.messageHeader);
-                       elHide(elementData.messageFooter);
-                       
-                       var editorElement = elById(id);
-                       if (Environment.editor() === 'redactor') {
-                               window.setTimeout((function() {
-                                       if (this._options.quoteManager) {
-                                               this._options.quoteManager.setAlternativeEditor(id);
-                                       }
-                                       
-                                       UiScroll.element(this._activeElement);
-                               }).bind(this), 250);
-                       }
-                       else {
-                               editorElement.focus();
-                       }
-               },
-               
-               /**
-                * Restores the message view.
-                * 
-                * @protected
-                */
-               _restoreMessage: function() {
-                       var elementData = this._elements.get(this._activeElement);
-                       
-                       this._destroyEditor();
-                       
-                       elRemove(elementData.messageBodyEditor);
-                       elementData.messageBodyEditor = null;
-                       
-                       elShow(elementData.messageBody);
-                       elShow(elementData.messageFooter);
-                       elShow(elementData.messageHeader);
-                       this._activeElement.classList.remove('jsInvalidQuoteTarget');
-                       
-                       this._activeElement = null;
-                       
-                       if (this._options.quoteManager) {
-                               this._options.quoteManager.clearAlternativeEditor();
-                       }
-               },
-               
-               /**
-                * Saves the editor message.
-                * 
-                * @protected
-                */
-               _save: function() {
-                       var parameters = {
-                               containerID: this._options.containerId,
-                               data: {
-                                       message: ''
-                               },
-                               objectID: this._getObjectId(this._activeElement),
-                               removeQuoteIDs: (this._options.quoteManager) ? this._options.quoteManager.getQuotesMarkedForRemoval() : []
-                       };
-                       
-                       var id = this._getEditorId();
-                       
-                       // add any available settings
-                       var settingsContainer = elById('settings_' + id);
-                       if (settingsContainer) {
-                               elBySelAll('input, select, textarea', settingsContainer, function (element) {
-                                       if (element.nodeName === 'INPUT' && (element.type === 'checkbox' || element.type === 'radio')) {
-                                               if (!element.checked) {
-                                                       return;
-                                               }
-                                       }
-                                       
-                                       var name = element.name;
-                                       if (parameters.hasOwnProperty(name)) {
-                                               throw new Error("Variable overshadowing, key '" + name + "' is already present.");
-                                       }
-                                       
-                                       parameters[name] = element.value.trim();
-                               });
-                       }
-                       
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'getText_' + id, parameters.data);
-                       
-                       var validateResult = this._validate(parameters);
-                       
-                       if (!(validateResult instanceof Promise)) {
-                               if (validateResult === false) {
-                                       validateResult = Promise.reject();
-                               }
-                               else {
-                                       validateResult = Promise.resolve();
-                               }
-                       }
-                       
-                       validateResult.then(function () {
-                               EventHandler.fire('com.woltlab.wcf.redactor2', 'submit_' + id, parameters);
-                               
-                               Ajax.api(this, {
-                                       actionName: 'save',
-                                       parameters: parameters
-                               });
-                               
-                               this._hideEditor();
-                       }.bind(this), function(e) {
-                               console.log('Validation of post edit failed: '+ e);
-                       });
-               },
-               
-               /**
-                * Validates the message and invokes listeners to perform additional validation.
-                *
-                * @param       {Object}        parameters      request parameters
-                * @return      {boolean}       validation result
-                * @protected
-                */
-               _validate: function(parameters) {
-                       // remove all existing error elements
-                       elBySelAll('.innerError', this._activeElement, elRemove);
-                       
-                       var data = {
-                               api: this,
-                               parameters: parameters,
-                               valid: true,
-                               promises: []
-                       };
-                       
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'validate_' + this._getEditorId(), data);
-                       
-                       data.promises.push(Promise[data.valid ? 'resolve' : 'reject']());
-                       
-                       return Promise.all(data.promises);
-               },
-               
-               /**
-                * Throws an error by adding an inline error to target element.
-                *
-                * @param       {Element}       element         erroneous element
-                * @param       {string}        message         error message
-                */
-               throwError: function(element, message) {
-                       elInnerError(element, message);
-               },
-               
-               /**
-                * Shows the update message.
-                * 
-                * @param       {Object}        data            ajax response data
-                * @protected
-                */
-               _showMessage: function(data) {
-                       var activeElement = this._activeElement;
-                       var editorId = this._getEditorId();
-                       var elementData = this._elements.get(activeElement);
-                       var attachmentLists = elBySelAll('.attachmentThumbnailList, .attachmentFileList', elementData.messageFooter);
-                       
-                       // set new content
-                       //noinspection JSUnresolvedVariable
-                       DomUtil.setInnerHtml(DomTraverse.childByClass(elementData.messageBody, 'messageText'), data.returnValues.message);
-                       
-                       // handle attachment list
-                       //noinspection JSUnresolvedVariable
-                       if (typeof data.returnValues.attachmentList === 'string') {
-                               for (var i = 0, length = attachmentLists.length; i < length; i++) {
-                                       elRemove(attachmentLists[i]);
-                               }
-                               
-                               var element = elCreate('div');
-                               //noinspection JSUnresolvedVariable
-                               DomUtil.setInnerHtml(element, data.returnValues.attachmentList);
-                               
-                               var node;
-                               while (element.childNodes.length) {
-                                       node = element.childNodes[element.childNodes.length - 1];
-                                       elementData.messageFooter.insertBefore(node, elementData.messageFooter.firstChild);
-                               }
-                       }
-                       
-                       // handle poll
-                       //noinspection JSUnresolvedVariable
-                       if (typeof data.returnValues.poll === 'string') {
-                               // find current poll
-                               var poll = elBySel('.pollContainer', elementData.messageBody);
-                               if (poll !== null) {
-                                       // poll contain is wrapped inside `.jsInlineEditorHideContent`
-                                       elRemove(poll.parentNode);
-                               }
-                               
-                               var pollContainer = elCreate('div');
-                               pollContainer.className = 'jsInlineEditorHideContent';
-                               //noinspection JSUnresolvedVariable
-                               DomUtil.setInnerHtml(pollContainer, data.returnValues.poll);
-                               
-                               DomUtil.prepend(pollContainer, elementData.messageBody);
-                       }
-                       
-                       this._restoreMessage();
-                       
-                       this._updateHistory(this._getHash(this._getObjectId(activeElement)));
-                       
-                       EventHandler.fire('com.woltlab.wcf.redactor', 'autosaveDestroy_' + editorId);
-                       
-                       UiNotification.show();
-                       
-                       if (this._options.quoteManager) {
-                               this._options.quoteManager.clearAlternativeEditor();
-                               this._options.quoteManager.countQuotes();
-                       }
-               },
-               
-               /**
-                * Hides the editor from view.
-                * 
-                * @protected
-                */
-               _hideEditor: function() {
-                       var elementData = this._elements.get(this._activeElement);
-                       elHide(DomTraverse.childByClass(elementData.messageBodyEditor, 'editorContainer'));
-                       
-                       var icon = elCreate('span');
-                       icon.className = 'icon icon48 fa-spinner';
-                       elementData.messageBodyEditor.appendChild(icon);
-               },
-               
-               /**
-                * Restores the previously hidden editor.
-                * 
-                * @protected
-                */
-               _restoreEditor: function() {
-                       var elementData = this._elements.get(this._activeElement);
-                       var icon = elBySel('.fa-spinner', elementData.messageBodyEditor);
-                       elRemove(icon);
-                       
-                       var editorContainer = DomTraverse.childByClass(elementData.messageBodyEditor, 'editorContainer');
-                       if (editorContainer !== null) elShow(editorContainer);
-               },
-               
-               /**
-                * Destroys the editor instance.
-                * 
-                * @protected
-                */
-               _destroyEditor: function() {
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'autosaveDestroy_' + this._getEditorId());
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'destroy_' + this._getEditorId());
-               },
-               
-               /**
-                * Returns the hash added to the url after successfully editing a message.
-                * 
-                * @param       {int}   objectId        message object id
-                * @return      string
-                * @protected
-                */
-               _getHash: function(objectId) {
-                       return '#message' + objectId;
-               },
-               
-               /**
-                * Updates the history to avoid old content when going back in the browser
-                * history.
-                * 
-                * @param       {string}        hash    location hash
-                * @protected
-                */
-               _updateHistory: function(hash) {
-                       window.location.hash = hash;
-               },
-               
-               /**
-                * Returns the unique editor id.
-                * 
-                * @return      {string}        editor id
-                * @protected
-                */
-               _getEditorId: function() {
-                       return this._options.editorPrefix + this._getObjectId(this._activeElement);
-               },
-               
-               /**
-                * Returns the element's `data-object-id` value.
-                * 
-                * @param       {Element}       element         target element
-                * @return      {int}
-                * @protected
-                */
-               _getObjectId: function(element) {
-                       return ~~elData(element, 'object-id');
-               },
-               
-               _ajaxFailure: function(data) {
-                       var elementData = this._elements.get(this._activeElement);
-                       var editor = elBySel('.redactor-layer', elementData.messageBodyEditor);
-                       
-                       // handle errors occurring on editor load
-                       if (editor === null) {
-                               this._restoreMessage();
-                               
-                               return true;
-                       }
-                       
-                       this._restoreEditor();
-                       
-                       //noinspection JSUnresolvedVariable
-                       if (!data || data.returnValues === undefined || data.returnValues.realErrorMessage === undefined) {
-                               return true;
-                       }
-                       
-                       //noinspection JSUnresolvedVariable
-                       elInnerError(editor, data.returnValues.realErrorMessage);
-                       
-                       return false;
-               },
-               
-               _ajaxSuccess: function(data) {
-                       switch (data.actionName) {
-                               case 'beginEdit':
-                                       this._showEditor(data);
-                                       break;
-                                       
-                               case 'save':
-                                       this._showMessage(data);
-                                       break;
-                       }
-               },
-               
-               _ajaxSetup: function() {
-                       return {
-                               data: {
-                                       className: this._options.className,
-                                       interfaceName: 'wcf\\data\\IMessageInlineEditorAction'
-                               },
-                               silent: true
-                       };
-               },
-               
-               /** @deprecated 3.0 - used only for backward compatibility with `WCF.Message.InlineEditor` */
-               legacyEdit: function(containerId) {
-                       this._click(elById(containerId), null);
-               }
-       };
-       
-       return UiMessageInlineEditor;
-});
-
-/**
- * Provides access and editing of message properties.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Message/Manager
- */
-define('WoltLabSuite/Core/Ui/Message/Manager',['Ajax', 'Core', 'Dictionary', 'Language', 'Dom/ChangeListener', 'Dom/Util'], function(Ajax, Core, Dictionary, Language, DomChangeListener, DomUtil) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       rebuild: function() {},
-                       getPermission: function() {},
-                       getPropertyValue: function() {},
-                       update: function() {},
-                       updateItems: function() {},
-                       updateAllItems: function() {},
-                       setNote: function() {},
-                       _update: function() {},
-                       _updateState: function() {},
-                       _toggleMessageStatus: function() {},
-                       _getAttributeName: function() {},
-                       _ajaxSuccess: function() {},
-                       _ajaxSetup: function() {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * @param       {Object}        options         initialization options
-        * @constructor
-        */
-       function UiMessageManager(options) { this.init(options); }
-       UiMessageManager.prototype = {
-               /**
-                * Initializes a new manager instance.
-                * 
-                * @param       {Object}        options         initialization options
-                */
-               init: function(options) {
-                       this._elements = null;
-                       this._options = Core.extend({
-                               className: '',
-                               selector: ''
-                       }, options);
-                       
-                       this.rebuild();
-                       
-                       DomChangeListener.add('Ui/Message/Manager' + this._options.className, this.rebuild.bind(this));
-               },
-               
-               /**
-                * Rebuilds the list of observed messages. You should call this method whenever a
-                * message has been either added or removed from the document.
-                */
-               rebuild: function() {
-                       this._elements = new Dictionary();
-                       
-                       var element, elements = elBySelAll(this._options.selector);
-                       for (var i = 0, length = elements.length; i < length; i++) {
-                               element = elements[i];
-                               
-                               this._elements.set(elData(element, 'object-id'), element);
-                       }
-               },
-               
-               /**
-                * Returns a boolean value for the given permission. The permission should not start
-                * with "can" or "can-" as this is automatically assumed by this method.
-                * 
-                * @param       {int}           objectId        message object id 
-                * @param       {string}        permission      permission name without a leading "can" or "can-"
-                * @return      {boolean}       true if permission was set and is either 'true' or '1'
-                */
-               getPermission: function(objectId, permission) {
-                       permission = 'can-' + this._getAttributeName(permission);
-                       var element = this._elements.get(objectId);
-                       if (element === undefined) {
-                               throw new Error("Unknown object id '" + objectId + "' for selector '" + this._options.selector + "'");
-                       }
-                       
-                       return elDataBool(element, permission);
-               },
-               
-               /**
-                * Returns the given property value from a message, optionally supporting a boolean return value.
-                * 
-                * @param       {int}           objectId        message object id
-                * @param       {string}        propertyName    attribute name
-                * @param       {boolean}       asBool          attempt to interpret property value as boolean
-                * @return      {(boolean|string)}      raw property value or boolean if requested
-                */
-               getPropertyValue: function(objectId, propertyName, asBool) {
-                       var element = this._elements.get(objectId);
-                       if (element === undefined) {
-                               throw new Error("Unknown object id '" + objectId + "' for selector '" + this._options.selector + "'");
-                       }
-                       
-                       return window[(asBool ? 'elDataBool' : 'elData')](element, this._getAttributeName(propertyName));
-               },
-               
-               /**
-                * Invokes a method for given message object id in order to alter its state or properties.
-                * 
-                * @param       {int}           objectId        message object id
-                * @param       {string}        actionName      action name used for the ajax api
-                * @param       {Object=}       parameters      optional list of parameters included with the ajax request
-                */
-               update: function(objectId, actionName, parameters) {
-                       Ajax.api(this, {
-                               actionName: actionName,
-                               parameters: parameters || {},
-                               objectIDs: [objectId]
-                       });
-               },
-               
-               /**
-                * Updates properties and states for given object ids. Keep in mind that this method does
-                * not support setting individual properties per message, instead all property changes
-                * are applied to all matching message objects.
-                * 
-                * @param       {Array<int>}    objectIds       list of message object ids
-                * @param       {Object}        data            list of updated properties
-                */
-               updateItems: function(objectIds, data) {
-                       if (!Array.isArray(objectIds)) {
-                               objectIds = [objectIds];
-                       }
-                       
-                       var element;
-                       for (var i = 0, length = objectIds.length; i < length; i++) {
-                               element = this._elements.get(objectIds[i]);
-                               if (element === undefined) {
-                                       continue;
-                               }
-                               
-                               for (var key in data) {
-                                       if (data.hasOwnProperty(key)) {
-                                               this._update(element, key, data[key]);
-                                       }
-                               }
-                       }
-               },
-               
-               /**
-                * Bulk updates the properties and states for all observed messages at once.
-                * 
-                * @param       {Object}        data            list of updated properties
-                */
-               updateAllItems: function(data) {
-                       var objectIds = [];
-                       this._elements.forEach((function(element, objectId) {
-                               objectIds.push(objectId);
-                       }).bind(this));
-                       
-                       this.updateItems(objectIds, data);
-               },
-               
-               /**
-                * Sets or removes a message note identified by its unique CSS class.
-                * 
-                * @param       {int}           objectId        message object id
-                * @param       {string}        className       unique CSS class
-                * @param       {string}        htmlContent     HTML content
-                */
-               setNote: function (objectId, className, htmlContent) {
-                       var element = this._elements.get(objectId);
-                       if (element === undefined) {
-                               throw new Error("Unknown object id '" + objectId + "' for selector '" + this._options.selector + "'");
-                       }
-                       
-                       var messageFooterNotes = elBySel('.messageFooterNotes', element);
-                       var note = elBySel('.' + className, messageFooterNotes);
-                       if (htmlContent) {
-                               if (note === null) {
-                                       note = elCreate('p');
-                                       note.className = 'messageFooterNote ' + className;
-                                       
-                                       messageFooterNotes.appendChild(note);
-                               }
-                               
-                               note.innerHTML = htmlContent;
-                       }
-                       else if (note !== null) {
-                               elRemove(note);
-                       }
-               },
-               
-               /**
-                * Updates a single property of a message element.
-                * 
-                * @param       {Element}       element         message element
-                * @param       {string}        propertyName    property name
-                * @param       {?}             propertyValue   property value, will be implicitly converted to string
-                * @protected
-                */
-               _update: function(element, propertyName, propertyValue) {
-                       elData(element, this._getAttributeName(propertyName), propertyValue);
-                       
-                       // handle special properties
-                       var propertyValueBoolean = (propertyValue == 1 || propertyValue === true || propertyValue === 'true');
-                       this._updateState(element, propertyName, propertyValue, propertyValueBoolean);
-               },
-               
-               /**
-                * Updates the message element's state based upon a property change.
-                * 
-                * @param       {Element}       element                 message element
-                * @param       {string}        propertyName            property name
-                * @param       {?}             propertyValue           property value
-                * @param       {boolean}       propertyValueBoolean    true if `propertyValue` equals either 'true' or '1'
-                * @protected
-                */
-               _updateState: function(element, propertyName, propertyValue, propertyValueBoolean) {
-                       switch (propertyName) {
-                               case 'isDeleted':
-                                       element.classList[(propertyValueBoolean ? 'add' : 'remove')]('messageDeleted');
-                                       this._toggleMessageStatus(element, 'jsIconDeleted', 'wcf.message.status.deleted', 'red', propertyValueBoolean);
-                                       
-                                       break;
-                               
-                               case 'isDisabled':
-                                       element.classList[(propertyValueBoolean ? 'add' : 'remove')]('messageDisabled');
-                                       this._toggleMessageStatus(element, 'jsIconDisabled', 'wcf.message.status.disabled', 'green', propertyValueBoolean);
-                                       
-                                       break;
-                       }
-               },
-               
-               /**
-                * Toggles the message status bade for provided element.
-                * 
-                * @param       {Element}       element         message element
-                * @param       {string}        className       badge class name
-                * @param       {string}        phrase          language phrase
-                * @param       {string}        badgeColor      color css class
-                * @param       {boolean}       addBadge        add or remove badge
-                * @protected
-                */
-               _toggleMessageStatus: function(element, className, phrase, badgeColor, addBadge) {
-                       var messageStatus = elBySel('.messageStatus', element);
-                       if (messageStatus === null) {
-                               var messageHeaderMetaData = elBySel('.messageHeaderMetaData', element);
-                               if (messageHeaderMetaData === null) {
-                                       // can't find appropriate location to insert badge
-                                       return;
-                               }
-                               
-                               messageStatus = elCreate('ul');
-                               messageStatus.className = 'messageStatus';
-                               DomUtil.insertAfter(messageStatus, messageHeaderMetaData);
-                       }
-                       
-                       var badge = elBySel('.' + className, messageStatus);
-                       
-                       if (addBadge) {
-                               if (badge !== null) {
-                                       // badge already exists
-                                       return;
-                               }
-                               
-                               badge = elCreate('span');
-                               badge.className = 'badge label ' + badgeColor + ' ' + className;
-                               badge.textContent = Language.get(phrase);
-                               
-                               var listItem = elCreate('li');
-                               listItem.appendChild(badge);
-                               messageStatus.appendChild(listItem);
-                       }
-                       else {
-                               if (badge === null) {
-                                       // badge does not exist
-                                       return;
-                               }
-                               
-                               elRemove(badge.parentNode);
-                       }
-               },
-               
-               /**
-                * Transforms camel-cased property names into their attribute equivalent.
-                * 
-                * @param       {string}        propertyName    camel-cased property name
-                * @return      {string}        equivalent attribute name
-                * @protected
-                */
-               _getAttributeName: function(propertyName) {
-                       if (propertyName.indexOf('-') !== -1) {
-                               return propertyName;
-                       }
-                       
-                       var attributeName = '';
-                       var str, tmp = propertyName.split(/([A-Z][a-z]+)/);
-                       for (var i = 0, length = tmp.length; i < length; i++) {
-                               str = tmp[i];
-                               if (str.length) {
-                                       if (attributeName.length) attributeName += '-';
-                                       attributeName += str.toLowerCase();
-                               }
-                       }
-                       
-                       return attributeName;
-               },
-               
-               _ajaxSuccess: function() {
-                       throw new Error("Method _ajaxSuccess() must be implemented by deriving functions.");
-               },
-               
-               _ajaxSetup: function() {
-                       return {
-                               data: {
-                                       className: this._options.className
-                               }
-                       };
-               }
-       };
-       
-       return UiMessageManager;
-});
-/**
- * Handles user interaction with the quick reply feature.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Message/Reply
- */
-define('WoltLabSuite/Core/Ui/Message/Reply',['Ajax', 'Core', 'EventHandler', 'Language', 'Dom/ChangeListener', 'Dom/Util', 'Dom/Traverse', 'Ui/Dialog', 'Ui/Notification', 'WoltLabSuite/Core/Ui/Scroll', 'EventKey', 'User', 'WoltLabSuite/Core/Controller/Captcha'],
-       function(Ajax, Core, EventHandler, Language, DomChangeListener, DomUtil, DomTraverse, UiDialog, UiNotification, UiScroll, EventKey, User, ControllerCaptcha) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _submitGuestDialog: function() {},
-                       _submit: function() {},
-                       _validate: function() {},
-                       throwError: function() {},
-                       _showLoadingOverlay: function() {},
-                       _hideLoadingOverlay: function() {},
-                       _reset: function() {},
-                       _handleError: function() {},
-                       _getEditor: function() {},
-                       _insertMessage: function() {},
-                       _ajaxSuccess: function() {},
-                       _ajaxFailure: function() {},
-                       _ajaxSetup: function() {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * @constructor
-        */
-       function UiMessageReply(options) { this.init(options); }
-       UiMessageReply.prototype = {
-               /**
-                * Initializes a new quick reply field.
-                * 
-                * @param       {Object}        options         configuration options
-                */
-               init: function(options) {
-                       this._options = Core.extend({
-                               ajax: {
-                                       className: ''
-                               },
-                               quoteManager: null,
-                               successMessage: 'wcf.global.success.add'
-                       }, options);
-                       
-                       this._container = elById('messageQuickReply');
-                       this._content = elBySel('.messageContent', this._container);
-                       this._textarea = elById('text');
-                       this._editor = null;
-                       this._guestDialogId = '';
-                       this._loadingOverlay = null;
-                       
-                       // prevent marking of text for quoting
-                       elBySel('.message', this._container).classList.add('jsInvalidQuoteTarget');
-                       
-                       // handle submit button
-                       var submitCallback = this._submit.bind(this);
-                       var submitButton = elBySel('button[data-type="save"]', this._container);
-                       submitButton.addEventListener(WCF_CLICK_EVENT, submitCallback);
-                       
-                       // bind reply button
-                       var replyButtons = elBySelAll('.jsQuickReply');
-                       for (var i = 0, length = replyButtons.length; i < length; i++) {
-                               replyButtons[i].addEventListener(WCF_CLICK_EVENT, (function(event) {
-                                       event.preventDefault();
-                                       
-                                       this._getEditor().WoltLabReply.showEditor();
-                                       
-                                       UiScroll.element(this._container, (function() {
-                                               this._getEditor().WoltLabCaret.endOfEditor();
-                                       }).bind(this));
-                               }).bind(this));
-                       }
-               },
-               
-               /**
-                * Submits the guest dialog.
-                * 
-                * @param       {Event}         event
-                * @protected
-                */
-               _submitGuestDialog: function(event) {
-                       // only submit when enter key is pressed
-                       if (event.type === 'keypress' && !EventKey.Enter(event)) {
-                               return;
-                       }
-                       
-                       var usernameInput = elBySel('input[name=username]', event.currentTarget.closest('.dialogContent'));
-                       if (usernameInput.value === '') {
-                               elInnerError(usernameInput, Language.get('wcf.global.form.error.empty'));
-                               usernameInput.closest('dl').classList.add('formError');
-                               
-                               return;
-                       }
-                       
-                       var parameters = {
-                               parameters: {
-                                       data: {
-                                               username: usernameInput.value
-                                       }
-                               }
-                       };
-                       
-                       //noinspection JSCheckFunctionSignatures
-                       var captchaId = elData(event.currentTarget, 'captcha-id');
-                       if (ControllerCaptcha.has(captchaId)) {
-                               var data = ControllerCaptcha.getData(captchaId);
-                               if (data instanceof Promise) {
-                                       data.then((function (data) {
-                                               parameters = Core.extend(parameters, data);
-                                               this._submit(undefined, parameters);
-                                       }).bind(this));
-                               }
-                               else {
-                                       parameters = Core.extend(parameters, ControllerCaptcha.getData(captchaId));
-                                       this._submit(undefined, parameters);
-                               }
-                       }
-                       else {
-                               this._submit(undefined, parameters);
-                       }
-               },
-               
-               /**
-                * Validates the message and submits it to the server.
-                * 
-                * @param       {Event?}        event                   event object
-                * @param       {Object?}       additionalParameters    additional parameters sent to the server
-                * @protected
-                */
-               _submit: function(event, additionalParameters) {
-                       if (event) {
-                               event.preventDefault();
-                       }
-                       
-                       // Ignore requests to submit the message while a previous request is still pending.
-                       if (this._content.classList.contains('loading')) {
-                               if (!this._guestDialogId || !UiDialog.isOpen(this._guestDialogId)) {
-                                       return;
-                               }
-                       }
-                       
-                       if (!this._validate()) {
-                               // validation failed, bail out
-                               return;
-                       }
-                       
-                       this._showLoadingOverlay();
-                       
-                       // build parameters
-                       var parameters = DomUtil.getDataAttributes(this._container, 'data-', true, true);
-                       parameters.data = { message: this._getEditor().code.get() };
-                       parameters.removeQuoteIDs = (this._options.quoteManager) ? this._options.quoteManager.getQuotesMarkedForRemoval() : [];
-                       
-                       // add any available settings
-                       var settingsContainer = elById('settings_text');
-                       if (settingsContainer) {
-                               elBySelAll('input, select, textarea', settingsContainer, function (element) {
-                                       if (element.nodeName === 'INPUT' && (element.type === 'checkbox' || element.type === 'radio')) {
-                                               if (!element.checked) {
-                                                       return;
-                                               }
-                                       }
-                                       
-                                       var name = element.name;
-                                       if (parameters.hasOwnProperty(name)) {
-                                               throw new Error("Variable overshadowing, key '" + name + "' is already present.");
-                                       }
-                                       
-                                       parameters[name] = element.value.trim();
-                               });
-                       }
-                       
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'submit_text', parameters.data);
-                       
-                       if (!User.userId && !additionalParameters) {
-                               parameters.requireGuestDialog = true;
-                       }
-                       
-                       Ajax.api(this, Core.extend({
-                               parameters: parameters
-                       }, additionalParameters));
-               },
-               
-               /**
-                * Validates the message and invokes listeners to perform additional validation.
-                * 
-                * @return      {boolean}       validation result
-                * @protected
-                */
-               _validate: function() {
-                       // remove all existing error elements
-                       elBySelAll('.innerError', this._container, elRemove);
-                       
-                       // check if editor contains actual content
-                       if (this._getEditor().utils.isEmpty()) {
-                               this.throwError(this._textarea, Language.get('wcf.global.form.error.empty'));
-                               return false;
-                       }
-                       
-                       var data = {
-                               api: this,
-                               editor: this._getEditor(),
-                               message: this._getEditor().code.get(),
-                               valid: true
-                       };
-                       
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'validate_text', data);
-                       
-                       return (data.valid !== false);
-               },
-               
-               /**
-                * Throws an error by adding an inline error to target element.
-                * 
-                * @param       {Element}       element         erroneous element
-                * @param       {string}        message         error message
-                */
-               throwError: function(element, message) {
-                       elInnerError(element, (message === 'empty' ? Language.get('wcf.global.form.error.empty') : message));
-               },
-               
-               /**
-                * Displays a loading spinner while the request is processed by the server.
-                * 
-                * @protected
-                */
-               _showLoadingOverlay: function() {
-                       if (this._loadingOverlay === null) {
-                               this._loadingOverlay = elCreate('div');
-                               this._loadingOverlay.className = 'messageContentLoadingOverlay';
-                               this._loadingOverlay.innerHTML = '<span class="icon icon96 fa-spinner"></span>';
-                       }
-                       
-                       this._content.classList.add('loading');
-                       this._content.appendChild(this._loadingOverlay);
-               },
-               
-               /**
-                * Hides the loading spinner.
-                * 
-                * @protected
-                */
-               _hideLoadingOverlay: function() {
-                       this._content.classList.remove('loading');
-                       
-                       var loadingOverlay = elBySel('.messageContentLoadingOverlay', this._content);
-                       if (loadingOverlay !== null) {
-                               loadingOverlay.parentNode.removeChild(loadingOverlay);
-                       }
-               },
-               
-               /**
-                * Resets the editor contents and notifies event listeners.
-                * 
-                * @protected
-                */
-               _reset: function() {
-                       this._getEditor().code.set('<p>\u200b</p>');
-                       
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'reset_text');
-               },
-               
-               /**
-                * Handles errors occurred during server processing.
-                * 
-                * @param       {Object}        data    response data
-                * @protected
-                */
-               _handleError: function(data) {
-                       var parameters = {
-                               api: this,
-                               cancel: false,
-                               returnValues: data.returnValues
-                       };
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'handleError_text', parameters);
-                       
-                       if (parameters.cancel !== true) {
-                               //noinspection JSUnresolvedVariable
-                               this.throwError(this._textarea, data.returnValues.realErrorMessage);
-                       }
-               },
-               
-               /**
-                * Returns the current editor instance.
-                * 
-                * @return      {Object}       editor instance
-                * @protected
-                */
-               _getEditor: function() {
-                       if (this._editor === null) {
-                               if (typeof window.jQuery === 'function') {
-                                       this._editor = window.jQuery(this._textarea).data('redactor');
-                               }
-                               else {
-                                       throw new Error("Unable to access editor, jQuery has not been loaded yet.");
-                               }
-                       }
-                       
-                       return this._editor;
-               },
-               
-               /**
-                * Inserts the rendered message into the post list, unless the post is on the next
-                * page in which case a redirect will be performed instead.
-                * 
-                * @param       {Object}        data    response data
-                * @protected
-                */
-               _insertMessage: function(data) {
-                       this._getEditor().WoltLabAutosave.reset();
-                       
-                       // redirect to new page
-                       //noinspection JSUnresolvedVariable
-                       if (data.returnValues.url) {
-                               //noinspection JSUnresolvedVariable
-                               if (window.location == data.returnValues.url) {
-                                       window.location.reload();
-                               }
-                               window.location = data.returnValues.url;
-                       }
-                       else {
-                               //noinspection JSUnresolvedVariable
-                               if (data.returnValues.template) {
-                                       var elementId;
-                                       
-                                       // insert HTML
-                                       if (elData(this._container, 'sort-order') === 'DESC') {
-                                               //noinspection JSUnresolvedVariable
-                                               DomUtil.insertHtml(data.returnValues.template, this._container, 'after');
-                                               elementId = DomUtil.identify(this._container.nextElementSibling);
-                                       }
-                                       else {
-                                               var insertBefore = this._container;
-                                               if (insertBefore.previousElementSibling && insertBefore.previousElementSibling.classList.contains('messageListPagination')) {
-                                                       insertBefore = insertBefore.previousElementSibling;
-                                               }
-                                               
-                                               //noinspection JSUnresolvedVariable
-                                               DomUtil.insertHtml(data.returnValues.template, insertBefore, 'before');
-                                               elementId = DomUtil.identify(insertBefore.previousElementSibling);
-                                       }
-                                       
-                                       // update last post time
-                                       //noinspection JSUnresolvedVariable
-                                       elData(this._container, 'last-post-time', data.returnValues.lastPostTime);
-                                       
-                                       window.history.replaceState(undefined, '', '#' + elementId);
-                                       UiScroll.element(elById(elementId));
-                               }
-                               
-                               UiNotification.show(Language.get(this._options.successMessage));
-                               
-                               if (this._options.quoteManager) {
-                                       this._options.quoteManager.countQuotes();
-                               }
-                               
-                               DomChangeListener.trigger();
-                       }
-               },
-               
-               /**
-                * @param {{returnValues:{guestDialog:string,guestDialogID:string}}} data
-                * @protected
-                */
-               _ajaxSuccess: function(data) {
-                       if (!User.userId && !data.returnValues.guestDialogID) {
-                               throw new Error("Missing 'guestDialogID' return value for guest.");
-                       }
-                       
-                       if (!User.userId && data.returnValues.guestDialog) {
-                               UiDialog.openStatic(data.returnValues.guestDialogID, data.returnValues.guestDialog, {
-                                       closable: false,
-                                       onClose: function() {
-                                               if (ControllerCaptcha.has(data.returnValues.guestDialogID)) {
-                                                       ControllerCaptcha.delete(data.returnValues.guestDialogID);
-                                               }
-                                       },
-                                       title: Language.get('wcf.global.confirmation.title')
-                               });
-                               
-                               var dialog = UiDialog.getDialog(data.returnValues.guestDialogID);
-                               elBySel('input[type=submit]', dialog.content).addEventListener(WCF_CLICK_EVENT, this._submitGuestDialog.bind(this));
-                               elBySel('input[type=text]', dialog.content).addEventListener('keypress', this._submitGuestDialog.bind(this));
-                               
-                               this._guestDialogId = data.returnValues.guestDialogID;
-                       }
-                       else {
-                               this._insertMessage(data);
-                               
-                               if (!User.userId) {
-                                       UiDialog.close(data.returnValues.guestDialogID);
-                               }
-                               
-                               this._reset();
-                               
-                               this._hideLoadingOverlay();
-                       }
-               },
-               
-               _ajaxFailure: function(data) {
-                       this._hideLoadingOverlay();
-                       
-                       //noinspection JSUnresolvedVariable
-                       if (data === null || data.returnValues === undefined || data.returnValues.realErrorMessage === undefined) {
-                               return true;
-                       }
-                       
-                       this._handleError(data);
-                       
-                       return false;
-               },
-               
-               _ajaxSetup: function() {
-                       return {
-                               data: {
-                                       actionName: 'quickReply',
-                                       className: this._options.ajax.className,
-                                       interfaceName: 'wcf\\data\\IMessageQuickReplyAction'
-                               },
-                               silent: true
-                       };
-               }
-       };
-       
-       return UiMessageReply;
-});
-
-/**
- * Provides buttons to share a page through multiple social community sites.
- *
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Message/Share
- */
-define('WoltLabSuite/Core/Ui/Message/Share',['EventHandler', 'StringUtil'], function(EventHandler, StringUtil) {
-       "use strict";
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Message/Share
-        */
-       return {
-               _pageDescription: '',
-               _pageUrl: '',
-               
-               init: function() {
-                       var title = elBySel('meta[property="og:title"]');
-                       if (title !== null) this._pageDescription = encodeURIComponent(title.content);
-                       var url = elBySel('meta[property="og:url"]');
-                       if (url !== null) this._pageUrl = encodeURIComponent(url.content);
-                       
-                       elBySelAll('.jsMessageShareButtons', null, (function(container) {
-                               container.classList.remove('jsMessageShareButtons');
-                               
-                               var pageUrl = encodeURIComponent(StringUtil.unescapeHTML(elData(container, 'url') || ''));
-                               if (!pageUrl) {
-                                       pageUrl = this._pageUrl;
-                               }
-                               
-                               var providers = {
-                                       facebook: {
-                                               link: elBySel('.jsShareFacebook', container),
-                                               share: (function(event) { this._share('facebook', 'https://www.facebook.com/sharer.php?u={pageURL}&t={text}', true, pageUrl); }).bind(this)
-                                       },
-                                       google: {
-                                               link: elBySel('.jsShareGoogle', container),
-                                               share: (function(event) { this._share('google', 'https://plus.google.com/share?url={pageURL}', false, pageUrl); }).bind(this)
-                                       },
-                                       reddit: {
-                                               link: elBySel('.jsShareReddit', container),
-                                               share: (function(event) { this._share('reddit', 'https://ssl.reddit.com/submit?url={pageURL}', false, pageUrl); }).bind(this)
-                                       },
-                                       twitter: {
-                                               link: elBySel('.jsShareTwitter', container),
-                                               share: (function(event) { this._share('twitter', 'https://twitter.com/share?url={pageURL}&text={text}', false, pageUrl); }).bind(this)
-                                       },
-                                       linkedIn: {
-                                               link: elBySel('.jsShareLinkedIn', container),
-                                               share: (function(event) { this._share('linkedIn', 'https://www.linkedin.com/cws/share?url={pageURL}', false, pageUrl); }).bind(this)
-                                       },
-                                       pinterest: {
-                                               link: elBySel('.jsSharePinterest', container),
-                                               share: (function(event) { this._share('pinterest', 'https://www.pinterest.com/pin/create/link/?url={pageURL}&description={text}', false, pageUrl); }).bind(this)
-                                       },
-                                       xing: {
-                                               link: elBySel('.jsShareXing', container),
-                                               share: (function(event) { this._share('xing', 'https://www.xing.com/social_plugins/share?url={pageURL}', false, pageUrl); }).bind(this)
-                                       },
-                                       whatsApp: {
-                                               link: elBySel('.jsShareWhatsApp', container),
-                                               share: (function() {
-                                                       window.location.href = 'whatsapp://send?text=' + this._pageDescription + '%20' + pageUrl;
-                                               }).bind(this)
-                                       }
-                               };
-                               
-                               EventHandler.fire('com.woltlab.wcf.message.share', 'shareProvider', {
-                                       container: container,
-                                       providers: providers,
-                                       pageDescription: this._pageDescription,
-                                       pageUrl: this._pageUrl
-                               });
-                               
-                               for (var provider in providers) {
-                                       if (providers.hasOwnProperty(provider)) {
-                                               if (providers[provider].link !== null) {
-                                                       providers[provider].link.addEventListener(WCF_CLICK_EVENT, providers[provider].share);
-                                               }
-                                       }
-                               }
-                       }).bind(this));
-               },
-               
-               _share: function(objectName, url, appendUrl, pageUrl) {
-                       // fallback for plugins
-                       if (!pageUrl) {
-                               pageUrl = this._pageUrl;
-                       }
-                       
-                       window.open(
-                               url.replace(/\{pageURL}/, pageUrl).replace(/\{text}/, this._pageDescription + (appendUrl ? "%20" + pageUrl : "")),
-                               objectName,
-                               'height=600,width=600'
-                       );
-               }
-       };
-});
-
-define('WoltLabSuite/Core/Ui/Page/Search',['Ajax', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog'], function(Ajax, EventKey, Language, StringUtil, DomUtil, UiDialog) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       open: function() {},
-                       _search: function() {},
-                       _click: function() {},
-                       _ajaxSuccess: function() {},
-                       _ajaxSetup: function() {},
-                       _dialogSetup: function() {}
-               };
-               return Fake;
-       }
-       
-       var _callbackSelect, _resultContainer, _resultList, _searchInput = null;
-       
-       return {
-               open: function(callbackSelect) {
-                       _callbackSelect = callbackSelect;
-                       
-                       UiDialog.open(this);
-               },
-               
-               _search: function (event) {
-                       event.preventDefault();
-                       
-                       var inputContainer = _searchInput.parentNode;
-                       
-                       var value = _searchInput.value.trim();
-                       if (value.length < 3) {
-                               elInnerError(inputContainer, Language.get('wcf.page.search.error.tooShort'));
-                               return;
-                       }
-                       else {
-                               elInnerError(inputContainer, false);
-                       }
-                       
-                       Ajax.api(this, {
-                               parameters: {
-                                       searchString: value
-                               }
-                       });
-               },
-               
-               _click: function (event) {
-                       event.preventDefault();
-                       
-                       var page = event.currentTarget;
-                       var pageTitle = elBySel('h3', page).textContent.replace(/['"]/g, '');
-                       
-                       _callbackSelect(elData(page, 'page-id') + '#' + pageTitle);
-                       
-                       UiDialog.close(this);
-               },
-               
-               _ajaxSuccess: function(data) {
-                       var html = '', page;
-                       //noinspection JSUnresolvedVariable
-                       for (var i = 0, length = data.returnValues.length; i < length; i++) {
-                               //noinspection JSUnresolvedVariable
-                               page = data.returnValues[i];
-                               
-                               html += '<li>'
-                                               + '<div class="containerHeadline pointer" data-page-id="' + page.pageID + '">'
-                                                       + '<h3>' + StringUtil.escapeHTML(page.name) + '</h3>'
-                                                       + '<small>' + StringUtil.escapeHTML(page.displayLink) + '</small>'
-                                               + '</div>'
-                                       + '</li>';
-                       }
-                       
-                       _resultList.innerHTML = html;
-                       
-                       window[html ? 'elShow' : 'elHide'](_resultContainer);
-                       
-                       if (html) {
-                               elBySelAll('.containerHeadline', _resultList, (function(item) {
-                                       item.addEventListener(WCF_CLICK_EVENT, this._click.bind(this));
-                               }).bind(this));
-                       }
-                       else {
-                               elInnerError(_searchInput.parentNode, Language.get('wcf.page.search.error.noResults'));
-                       }
-               },
-               
-               _ajaxSetup: function () {
-                       return {
-                               data: {
-                                       actionName: 'search',
-                                       className: 'wcf\\data\\page\\PageAction'
-                               }
-                       };
-               },
-               
-               _dialogSetup: function() {
-                       return {
-                               id: 'wcfUiPageSearch',
-                               options: {
-                                       onSetup: (function() {
-                                               var callbackSearch = this._search.bind(this);
-                                               
-                                               _searchInput = elById('wcfUiPageSearchInput');
-                                               _searchInput.addEventListener('keydown', function(event) {
-                                                       if (EventKey.Enter(event)) {
-                                                               callbackSearch(event);
-                                                       }
-                                               });
-                                               
-                                               _searchInput.nextElementSibling.addEventListener(WCF_CLICK_EVENT, callbackSearch);
-                                               
-                                               _resultContainer = elById('wcfUiPageSearchResultContainer');
-                                               _resultList = elById('wcfUiPageSearchResultList');
-                                       }).bind(this),
-                                       onShow: function() {
-                                               _searchInput.focus();
-                                       },
-                                       title: Language.get('wcf.page.search')
-                               },
-                               source: '<div class="section">'
-                                       + '<dl>'
-                                               + '<dt><label for="wcfUiPageSearchInput">' + Language.get('wcf.page.search.name') + '</label></dt>'
-                                               + '<dd>'
-                                                       + '<div class="inputAddon">'
-                                                               + '<input type="text" id="wcfUiPageSearchInput" class="long">'
-                                                               + '<a href="#" class="inputSuffix"><span class="icon icon16 fa-search"></span></a>'
-                                                       + '</div>'
-                                               + '</dd>'
-                                       + '</dl>'
-                               + '</div>'
-                               + '<section id="wcfUiPageSearchResultContainer" class="section" style="display: none;">'
-                                       + '<header class="sectionHeader">'
-                                               + '<h2 class="sectionTitle">' + Language.get('wcf.page.search.results') + '</h2>'
-                                       + '</header>'
-                                       + '<ol id="wcfUiPageSearchResultList" class="containerList"></ol>'
-                               + '</section>'
-                       };
-               }
-       };
-});
-
-/**
- * Sortable lists with optimized handling per device sizes.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Sortable/List
- */
-define('WoltLabSuite/Core/Ui/Sortable/List',['Core', 'Ui/Screen'], function (Core, UiScreen) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _enable: function() {},
-                       _disable: function() {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * @constructor
-        */
-       function UiSortableList(options) { this.init(options); }
-       UiSortableList.prototype = {
-               /**
-                * Initializes the sortable list controller.
-                * 
-                * @param       {Object}        options         initialization options for `WCF.Sortable.List`
-                */
-               init: function (options) {
-                       this._options = Core.extend({
-                               containerId: '',
-                               className: '',
-                               offset: 0,
-                               options: {},
-                               isSimpleSorting: false,
-                               additionalParameters: {}
-                       }, options);
-                       
-                       UiScreen.on('screen-sm-md', {
-                               match: this._enable.bind(this, true),
-                               unmatch: this._disable.bind(this),
-                               setup: this._enable.bind(this, true)
-                       });
-                       
-                       UiScreen.on('screen-lg', {
-                               match: this._enable.bind(this, false),
-                               unmatch: this._disable.bind(this),
-                               setup: this._enable.bind(this, false)
-                       });
-               },
-               
-               /**
-                * Enables sorting with an optional sort handle.
-                * 
-                * @param       {boolean}       hasHandle       true if sort can only be started with the sort handle
-                * @protected
-                */
-               _enable: function (hasHandle) {
-                       var options = this._options.options;
-                       if (hasHandle) options.handle = '.sortableNodeHandle';
-                       
-                       new window.WCF.Sortable.List(
-                               this._options.containerId,
-                               this._options.className,
-                               this._options.offset,
-                               options,
-                               this._options.isSimpleSorting,
-                               this._options.additionalParameters
-                       );
-               },
-               
-               /**
-                * Disables sorting for registered containers.
-                * 
-                * @protected
-                */
-               _disable: function () {
-                       window.jQuery('#' + this._options.containerId + ' .sortableList')[(this._options.isSimpleSorting ? 'sortable' : 'nestedSortable')]('destroy');
-               }
-       };
-       
-       return UiSortableList;
-});
-/**
- * Handles the data to create and edit a poll in a form created via form builder.
- * 
- * @author     Alexander Ebert, Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Poll/Editor
- * @since      5.2
- */
-define('WoltLabSuite/Core/Ui/Poll/Editor',[
-       'Core',
-       'Dom/Util',
-       'EventHandler',
-       'EventKey',
-       'Language',
-       'WoltLabSuite/Core/Date/Picker',
-       'WoltLabSuite/Core/Ui/Sortable/List'
-], function(
-       Core,
-       DomUtil,
-       EventHandler,
-       EventKey,
-       Language,
-       DatePicker,
-       UiSortableList
-) {
-       "use strict";
-       
-       function UiPollEditor(containerId, pollOptions, wysiwygId, options) {
-               this.init(containerId, pollOptions, wysiwygId, options);
-       }
-       UiPollEditor.prototype = {
-               /**
-                * Initializes the poll editor.
-                * 
-                * @param       {string}        containerId     id of the poll options container
-                * @param       {object[]}      pollOptions     existing poll options
-                * @param       {string}        wysiwygId       id of the related wysiwyg editor
-                * @param       {object}        options         additional poll options
-                */
-               init: function(containerId, pollOptions, wysiwygId, options) {
-                       this._container = elById(containerId);
-                       if (this._container === null) {
-                               throw new Error("Unknown poll editor container with id '" + containerId + "'.");
-                       }
-                       
-                       this._wysiwygId = wysiwygId;
-                       if (wysiwygId !== '' && elById(wysiwygId) === null) {
-                               throw new Error("Unknown wysiwyg field with id '" + wysiwygId + "'.");
-                       }
-                       
-                       this.questionField = elById(this._wysiwygId + 'Poll_question');
-                       
-                       var optionLists = elByClass('sortableList', this._container);
-                       if (optionLists.length === 0) {
-                               throw new Error("Cannot find poll options list for container with id '" + containerId + "'.");
-                       }
-                       this.optionList = optionLists[0];
-                       
-                       this.endTimeField = elById(this._wysiwygId + 'Poll_endTime');
-                       this.maxVotesField = elById(this._wysiwygId + 'Poll_maxVotes');
-                       this.isChangeableYesField = elById(this._wysiwygId + 'Poll_isChangeable');
-                       this.isChangeableNoField = elById(this._wysiwygId + 'Poll_isChangeable_no');
-                       this.isPublicYesField = elById(this._wysiwygId + 'Poll_isPublic');
-                       this.isPublicNoField = elById(this._wysiwygId + 'Poll_isPublic_no');
-                       this.resultsRequireVoteYesField = elById(this._wysiwygId + 'Poll_resultsRequireVote');
-                       this.resultsRequireVoteNoField = elById(this._wysiwygId + 'Poll_resultsRequireVote_no');
-                       this.sortByVotesYesField = elById(this._wysiwygId + 'Poll_sortByVotes');
-                       this.sortByVotesNoField = elById(this._wysiwygId + 'Poll_sortByVotes_no');
-                       
-                       this._optionCount = 0;
-                       this._options = Core.extend({
-                               isAjax: false,
-                               maxOptions: 20
-                       }, options);
-                       
-                       this._createOptionList(pollOptions || []);
-                       
-                       new UiSortableList({
-                               containerId: containerId,
-                               options: {
-                                       toleranceElement: '> div'
-                               }
-                       });
-                       
-                       if (this._options.isAjax) {
-                               var events = ['handleError', 'reset', 'submit', 'validate'];
-                               for (var i = 0, length = events.length; i < length; i++) {
-                                       var event = events[i];
-                                       
-                                       EventHandler.add(
-                                               'com.woltlab.wcf.redactor2',
-                                               event + '_' + this._wysiwygId,
-                                               this['_' + event].bind(this)
-                                       );
-                               }
-                       }
-                       else {
-                               var form = this._container.closest('form');
-                               if (form === null) {
-                                       throw new Error("Cannot find form for container with id '" + containerId + "'.");
-                               }
-                               
-                               form.addEventListener('submit', this._submit.bind(this));
-                       }
-               },
-               
-               /**
-                * Adds an option based on below the option for which the `Add Option` button has
-                * been clicked.
-                * 
-                * @param       {Event}         event           icon click event
-                */
-               _addOption: function(event) {
-                       event.preventDefault();
-                       
-                       if (this._optionCount === this._options.maxOptions) {
-                               return false;
-                       }
-                       
-                       this._createOption(
-                               undefined,
-                               undefined,
-                               event.currentTarget.closest('li')
-                       );
-               },
-               
-               /**
-                * Creates a new option based on the given data or an empty option if no option data
-                * is given.
-                * 
-                * @param       {string}        optionValue     value of the option
-                * @param       {integer}       optionId        id of the option
-                * @param       {Element?}      insertAfter     optional element after which the new option is added
-                * @private
-                */
-               _createOption: function(optionValue, optionId, insertAfter) {
-                       optionValue = optionValue || '';
-                       optionId = ~~optionId || 0;
-                       
-                       var listItem = elCreate('LI');
-                       listItem.className = 'sortableNode';
-                       elData(listItem, 'option-id', optionId);
-                       
-                       if (insertAfter) {
-                               DomUtil.insertAfter(listItem, insertAfter);
-                       }
-                       else {
-                               this.optionList.appendChild(listItem);
-                       }
-                       
-                       var pollOptionInput = elCreate('div');
-                       pollOptionInput.className = 'pollOptionInput';
-                       listItem.appendChild(pollOptionInput);
-                       
-                       var sortHandle = elCreate('span');
-                       sortHandle.className = 'icon icon16 fa-arrows sortableNodeHandle';
-                       pollOptionInput.appendChild(sortHandle);
-                       
-                       // buttons
-                       var addButton = elCreate('a');
-                       elAttr(addButton, 'role', 'button');
-                       elAttr(addButton, 'href', '#');
-                       addButton.className = 'icon icon16 fa-plus jsTooltip jsAddOption pointer';
-                       elAttr(addButton, 'title', Language.get('wcf.poll.button.addOption'));
-                       addButton.addEventListener('click', this._addOption.bind(this));
-                       pollOptionInput.appendChild(addButton);
-                       
-                       var deleteButton = elCreate('a');
-                       elAttr(deleteButton, 'role', 'button');
-                       elAttr(deleteButton, 'href', '#');
-                       deleteButton.className = 'icon icon16 fa-times jsTooltip jsDeleteOption pointer';
-                       elAttr(deleteButton, 'title', Language.get('wcf.poll.button.removeOption'));
-                       deleteButton.addEventListener('click', this._removeOption.bind(this));
-                       pollOptionInput.appendChild(deleteButton);
-                       
-                       // input field
-                       var optionInput = elCreate('input');
-                       elAttr(optionInput, 'type', 'text');
-                       optionInput.value = optionValue;
-                       elAttr(optionInput, 'maxlength', 255);
-                       optionInput.addEventListener('keydown', this._optionInputKeyDown.bind(this));
-                       optionInput.addEventListener('click', function() {
-                               // work-around for some weird focus issue on iOS/Android
-                               if (document.activeElement !== this) {
-                                       this.focus();
-                               }
-                       });
-                       pollOptionInput.appendChild(optionInput);
-                       
-                       if (insertAfter !== null) {
-                               optionInput.focus();
-                       }
-                       
-                       this._optionCount++;
-                       if (this._optionCount === this._options.maxOptions) {
-                               elBySelAll('span.jsAddOption', this.optionList, function(icon) {
-                                       icon.classList.remove('pointer');
-                                       icon.classList.add('disabled');
-                               });
-                       }
-               },
-               
-               /**
-                * Adds the given poll option to the option list.
-                * 
-                * @param       {object[]}      pollOptions     data of the added options
-                */
-               _createOptionList: function(pollOptions) {
-                       for (var i = 0, length = pollOptions.length; i < length; i++) {
-                               var option = pollOptions[i];
-                               this._createOption(option.optionValue, option.optionID);
-                       }
-                       
-                       // add empty option field to add new options
-                       if (this._optionCount < this._options.maxOptions) {
-                               this._createOption();
-                       }
-               },
-               
-               /**
-                * Handles errors when the data is saved via AJAX.
-                * 
-                * @param       {object}        data    request response data
-                */
-               _handleError: function (data) {
-                       switch (data.returnValues.fieldName) {
-                               case this._wysiwygId + 'Poll_endTime':
-                               case this._wysiwygId + 'Poll_maxVotes':
-                                       var fieldName = data.returnValues.fieldName.replace(this._wysiwygId + 'Poll_', '');
-                                       
-                                       var small = elCreate('small');
-                                       small.className = 'innerError';
-                                       small.innerHTML = Language.get('wcf.poll.' + fieldName + '.error.' + data.returnValues.errorType);
-                                       
-                                       var element = elById(data.returnValues.fieldName);
-                                       var errorParent = element.closest('dd');
-                                       
-                                       DomUtil.prepend(small, element.nextSibling);
-                                       
-                                       data.cancel = true;
-                                       break;
-                       }
-               },
-               
-               /**
-                * Adds an empty poll option after the current option when clicking enter.
-                * 
-                * @param       {Event}         event   key event
-                */
-               _optionInputKeyDown: function(event) {
-                       // ignore every key except for [Enter]
-                       if (!EventKey.Enter(event)) {
-                               return;
-                       }
-                       
-                       Core.triggerEvent(elByClass('jsAddOption', event.currentTarget.parentNode)[0], 'click');
-                       
-                       event.preventDefault();
-               },
-               
-               /**
-                * Removes a poll option after clicking on the `Remove Option` button.
-                * 
-                * @param       {Event}         event   click event
-                */
-               _removeOption: function (event) {
-                       event.preventDefault();
-                       
-                       elRemove(event.currentTarget.closest('li'));
-                       
-                       this._optionCount--;
-                       
-                       elBySelAll('span.jsAddOption', this.optionList, function(icon) {
-                               icon.classList.add('pointer');
-                               icon.classList.remove('disabled');
-                       });
-                       
-                       if (this.optionList.length === 0) {
-                               this._createOption();
-                       }
-               },
-               
-               /**
-                * Resets all poll-related form fields.
-                */
-               _reset: function() {
-                       this.questionField.value = '';
-                       
-                       this._optionCount = 0;
-                       this.optionList.innerHtml = '';
-                       this._createOption();
-                       
-                       DatePicker.clear(this.endTimeField);
-                       
-                       this.maxVotesField.value = 1;
-                       this.isChangeableYesField.checked = false;
-                       this.isChangeableNoField.checked = true;
-                       this.isPublicYesField = false;
-                       this.isPublicNoField = true;
-                       this.resultsRequireVoteYesField = false;
-                       this.resultsRequireVoteNoField = true;
-                       this.sortByVotesYesField = false;
-                       this.sortByVotesNoField = true;
-                       
-                       EventHandler.fire(
-                               'com.woltlab.wcf.poll.editor',
-                               'reset',
-                               {
-                                       pollEditor: this
-                               }
-                       );
-               },
-               
-               /**
-                * Is called if the form is submitted or before the AJAX request is sent.
-                * 
-                * @param       {Event?}        event   form submit event
-                */
-               _submit: function(event) {
-                       var options = [];
-                       for (var i = 0, length = this.optionList.children.length; i < length; i++) {
-                               var listItem = this.optionList.children[i];
-                               var optionValue = elBySel('input[type=text]', listItem).value.trim();
-                               
-                               if (optionValue !== '') {
-                                       options.push(elData(listItem, 'option-id') + '_' + optionValue);
-                               }
-                       }
-                       
-                       if (this._options.isAjax) {
-                               event.poll = {};
-                               
-                               event.poll[this.questionField.id] = this.questionField.value;
-                               event.poll[this._wysiwygId + 'Poll_options'] = options;
-                               event.poll[this.endTimeField.id] = this.endTimeField.value;
-                               event.poll[this.maxVotesField.id] = this.maxVotesField.value;
-                               event.poll[this.isChangeableYesField.id] = !!this.isChangeableYesField.checked;
-                               event.poll[this.isPublicYesField.id] = !!this.isPublicYesField.checked;
-                               event.poll[this.resultsRequireVoteYesField.id] = !!this.resultsRequireVoteYesField.checked;
-                               event.poll[this.sortByVotesYesField.id] = !!this.sortByVotesYesField.checked;
-                               
-                               EventHandler.fire(
-                                       'com.woltlab.wcf.poll.editor',
-                                       'submit',
-                                       {
-                                               event: event,
-                                               pollEditor: this
-                                       }
-                               );
-                       }
-                       else {
-                               var form = this._container.closest('form');
-                               
-                               for (var i = 0, length = options.length; i < length; i++) {
-                                       var input = elCreate('input');
-                                       elAttr(input, 'type', 'hidden');
-                                       elAttr(input, 'name', this._wysiwygId + 'Poll_options[' + i + ']');
-                                       input.value = options[i];
-                                       form.appendChild(input);
-                               }
-                       }
-               },
-               
-               /**
-                * Is called to validate the poll data.
-                * 
-                * @param       {object}        data    event data
-                */
-               _validate: function(data) {
-                       if (this.questionField.value.trim() === '') {
-                               return;
-                       }
-                       
-                       var nonEmptyOptionCount = 0;
-                       for (var i = 0, length = this.optionList.children.length; i < length; i++) {
-                               var optionInput = elBySel('input[type=text]', this.optionList.children[i]);
-                               if (optionInput.value.trim() !== '') {
-                                       nonEmptyOptionCount++;
-                               }
-                       }
-                       
-                       if (nonEmptyOptionCount === 0) {
-                               data.api.throwError(this._container, Language.get('wcf.global.form.error.empty'));
-                               data.valid = false;
-                       }
-                       else {
-                               var maxVotes = ~~this.maxVotesField.value;
-                               
-                               if (maxVotes && maxVotes > nonEmptyOptionCount) {
-                                       data.api.throwError(this.maxVotesField.parentNode, Language.get('wcf.poll.maxVotes.error.invalid'));
-                                       data.valid = false;
-                               }
-                               else {
-                                       EventHandler.fire(
-                                               'com.woltlab.wcf.poll.editor',
-                                               'validate',
-                                               {
-                                                       data: data,
-                                                       pollEditor: this
-                                               }
-                                       );
-                               }
-                       }
-               }
-       };
-       
-       return UiPollEditor;
-});
-
-/**
- * Converts `<woltlab-metacode>` into the bbcode representation.
- * 
- * @author      Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module      WoltLabSuite/Core/Ui/Redactor/Article
- */
-define('WoltLabSuite/Core/Ui/Redactor/Article',['WoltLabSuite/Core/Ui/Article/Search'], function(UiArticleSearch) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _click: function() {},
-                       _insert: function() {}
-               };
-               return Fake;
-       }
-       
-       function UiRedactorArticle(editor, button) { this.init(editor, button); }
-       UiRedactorArticle.prototype = {
-               init: function (editor, button) {
-                       this._editor = editor;
-                       
-                       button.addEventListener(WCF_CLICK_EVENT, this._click.bind(this));
-               },
-               
-               _click: function (event) {
-                       event.preventDefault();
-                       
-                       UiArticleSearch.open(this._insert.bind(this));
-               },
-               
-               _insert: function (articleId) {
-                       this._editor.buffer.set();
-                       
-                       this._editor.insert.text("[wsa='" + articleId + "'][/wsa]");
-               }
-       };
-       
-       return UiRedactorArticle;
-});
-
-/**
- * Converts `<woltlab-metacode>` into the bbcode representation.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Redactor/Metacode
- */
-define('WoltLabSuite/Core/Ui/Redactor/Metacode',['EventHandler', 'Dom/Util'], function(EventHandler, DomUtil) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       convert: function() {},
-                       convertFromHtml: function() {},
-                       _getOpeningTag: function() {},
-                       _getClosingTag: function() {},
-                       _getFirstParagraph: function() {},
-                       _getLastParagraph: function() {},
-                       _parseAttributes: function() {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Redactor/Metacode
-        */
-       return {
-               /**
-                * Converts `<woltlab-metacode>` into the bbcode representation.
-                * 
-                * @param       {Element}       element         textarea element
-                */
-               convert: function(element) {
-                       element.textContent = this.convertFromHtml(element.textContent);
-               },
-               
-               convertFromHtml: function (editorId, html) {
-                       var div = elCreate('div');
-                       div.innerHTML = html;
-                       
-                       var attributes, data, metacode, metacodes = elByTag('woltlab-metacode', div), name, tagClose, tagOpen;
-                       while (metacodes.length) {
-                               metacode = metacodes[0];
-                               name = elData(metacode, 'name');
-                               attributes = this._parseAttributes(elData(metacode, 'attributes'));
-                               
-                               data = {
-                                       attributes: attributes,
-                                       cancel: false,
-                                       metacode: metacode
-                               };
-                               
-                               EventHandler.fire('com.woltlab.wcf.redactor2', 'metacode_' + name + '_' + editorId, data);
-                               if (data.cancel === true) {
-                                       continue;
-                               }
-                               
-                               tagOpen = this._getOpeningTag(name, attributes);
-                               tagClose = this._getClosingTag(name);
-                               
-                               if (metacode.parentNode === div) {
-                                       DomUtil.prepend(tagOpen, this._getFirstParagraph(metacode));
-                                       this._getLastParagraph(metacode).appendChild(tagClose);
-                               }
-                               else {
-                                       DomUtil.prepend(tagOpen, metacode);
-                                       metacode.appendChild(tagClose);
-                               }
-                               
-                               DomUtil.unwrapChildNodes(metacode);
-                       }
-                       
-                       // convert `<kbd>…</kbd>` to `[tt]…[/tt]`
-                       var inlineCode, inlineCodes = elByTag('kbd', div);
-                       while (inlineCodes.length) {
-                               inlineCode = inlineCodes[0];
-                               
-                               inlineCode.insertBefore(document.createTextNode('[tt]'), inlineCode.firstChild);
-                               inlineCode.appendChild(document.createTextNode('[/tt]'));
-                               
-                               DomUtil.unwrapChildNodes(inlineCode);
-                       }
-                       
-                       return div.innerHTML;
-               },
-               
-               /**
-                * Returns a text node representing the opening bbcode tag.
-                * 
-                * @param       {string}        name            bbcode tag
-                * @param       {Array}         attributes      list of attributes
-                * @returns     {Text}          text node containing the opening bbcode tag
-                * @protected
-                */
-               _getOpeningTag: function(name, attributes) {
-                       var buffer = '[' + name;
-                       if (attributes.length) {
-                               buffer += '=';
-                               
-                               for (var i = 0, length = attributes.length; i < length; i++) {
-                                       if (i > 0) buffer += ",";
-                                       buffer += "'" + attributes[i] + "'";
-                               }
-                       }
-                       
-                       return document.createTextNode(buffer + ']');
-               },
-               
-               /**
-                * Returns a text node representing the closing bbcode tag.
-                * 
-                * @param       {string}        name            bbcode tag
-                * @returns     {Text}          text node containing the closing bbcode tag
-                * @protected
-                */
-               _getClosingTag: function(name) {
-                       return document.createTextNode('[/' + name + ']');
-               },
-               
-               /**
-                * Returns the first paragraph of provided element. If there are no children or
-                * the first child is not a paragraph, a new paragraph is created and inserted
-                * as first child.
-                * 
-                * @param       {Element}       element         metacode element
-                * @returns     {Element}       paragraph that is the first child of provided element
-                * @protected
-                */
-               _getFirstParagraph: function (element) {
-                       var firstChild, paragraph;
-                       
-                       if (element.childElementCount === 0) {
-                               paragraph = elCreate('p');
-                               element.appendChild(paragraph);
-                       }
-                       else {
-                               firstChild = element.children[0];
-                               
-                               if (firstChild.nodeName === 'P') {
-                                       paragraph = firstChild;
-                               }
-                               else {
-                                       paragraph = elCreate('p');
-                                       element.insertBefore(paragraph, firstChild);
-                               }
-                       }
-                       
-                       return paragraph;
-               },
-               
-               /**
-                * Returns the last paragraph of provided element. If there are no children or
-                * the last child is not a paragraph, a new paragraph is created and inserted
-                * as last child.
-                * 
-                * @param       {Element}       element         metacode element
-                * @returns     {Element}       paragraph that is the last child of provided element
-                * @protected
-                */
-               _getLastParagraph: function (element) {
-                       var count = element.childElementCount, lastChild, paragraph;
-                       
-                       if (count === 0) {
-                               paragraph = elCreate('p');
-                               element.appendChild(paragraph);
-                       }
-                       else {
-                               lastChild = element.children[count - 1];
-                               
-                               if (lastChild.nodeName === 'P') {
-                                       paragraph = lastChild;
-                               }
-                               else {
-                                       paragraph = elCreate('p');
-                                       element.appendChild(paragraph);
-                               }
-                       }
-                       
-                       return paragraph;
-               },
-               
-               /**
-                * Parses the attributes string.
-                * 
-                * @param       {string}        attributes      base64- and JSON-encoded attributes
-                * @return      {Array}         list of parsed attributes
-                * @protected
-                */
-               _parseAttributes: function(attributes) {
-                       try {
-                               attributes = JSON.parse(atob(attributes));
-                       }
-                       catch (e) { /* invalid base64 data or invalid json */ }
-                       
-                       if (!Array.isArray(attributes)) {
-                               return [];
-                       }
-                       
-                       var attribute, parsedAttributes = [];
-                       for (var i = 0, length = attributes.length; i < length; i++) {
-                               attribute = attributes[i];
-                               
-                               if (typeof attribute === 'string') {
-                                       attribute = attribute.replace(/^'(.*)'$/, '$1');
-                               }
-                               
-                               parsedAttributes.push(attribute);
-                       }
-                       
-                       return parsedAttributes;
-               }
-       };
-});
-
-/**
- * Manages the autosave process storing the current editor message in the local
- * storage to recover it on browser crash or accidental navigation.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Redactor/Autosave
- */
-define('WoltLabSuite/Core/Ui/Redactor/Autosave',['Core', 'Devtools', 'EventHandler', 'Language', 'Dom/Traverse', './Metacode'], function(Core, Devtools, EventHandler, Language, DomTraverse, UiRedactorMetacode) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       getInitialValue: function() {},
-                       getMetaData: function () {},
-                       watch: function() {},
-                       destroy: function() {},
-                       clear: function() {},
-                       createOverlay: function() {},
-                       hideOverlay: function() {},
-                       _saveToStorage: function() {},
-                       _cleanup: function() {}
-               };
-               return Fake;
-       }
-       
-       // time between save requests in seconds
-       var _frequency = 15;
-       
-       /**
-        * @param       {Element}       element         textarea element
-        * @constructor
-        */
-       function UiRedactorAutosave(element) { this.init(element); }
-       UiRedactorAutosave.prototype = {
-               /**
-                * Initializes the autosave handler and removes outdated messages from storage.
-                * 
-                * @param       {Element}       element         textarea element
-                */
-               init: function (element) {
-                       this._container = null;
-                       this._metaData = {};
-                       this._editor = null;
-                       this._element = element;
-                       this._isActive = true;
-                       this._isPending = false;
-                       this._key = Core.getStoragePrefix() + elData(this._element, 'autosave');
-                       this._lastMessage = '';
-                       this._originalMessage = '';
-                       this._overlay = null;
-                       this._restored = false;
-                       this._timer = null;
-                       
-                       this._cleanup();
-                       
-                       // remove attribute to prevent Redactor's built-in autosave to kick in
-                       this._element.removeAttribute('data-autosave');
-                       
-                       var form = DomTraverse.parentByTag(this._element, 'FORM');
-                       if (form !== null) {
-                               form.addEventListener('submit', this.destroy.bind(this));
-                       }
-                       
-                       // export meta data
-                       EventHandler.add('com.woltlab.wcf.redactor2', 'getMetaData_' + this._element.id, (function (data) {
-                               for (var key in this._metaData) {
-                                       if (this._metaData.hasOwnProperty(key)) {
-                                               data[key] = this._metaData[key];
-                                       }
-                               }
-                       }).bind(this));
-                       
-                       // clear editor content on reset
-                       EventHandler.add('com.woltlab.wcf.redactor2', 'reset_' + this._element.id, this.hideOverlay.bind(this));
-                       
-                       document.addEventListener('visibilitychange', this._onVisibilityChange.bind(this));
-               },
-               
-               _onVisibilityChange: function () {
-                       if (document.hidden) {
-                               this._isActive = false;
-                               this._isPending = true;
-                       }
-                       else {
-                               this._isActive = true;
-                               this._isPending = false;
-                       }
-               },
-               
-               /**
-                * Returns the initial value for the textarea, used to inject message
-                * from storage into the editor before initialization.
-                * 
-                * @return      {string}        message content
-                */
-               getInitialValue: function() {
-                       //noinspection JSUnresolvedVariable
-                       if (window.ENABLE_DEVELOPER_TOOLS && Devtools._internal_.editorAutosave() === false) {
-                               //noinspection JSUnresolvedVariable
-                               return this._element.value;
-                       }
-                       
-                       var value = '';
-                       try {
-                               value = window.localStorage.getItem(this._key);
-                       }
-                       catch (e) {
-                               window.console.warn("Unable to access local storage: " + e.message);
-                       }
-                       
-                       try {
-                               value = JSON.parse(value);
-                       }
-                       catch (e) {
-                               value = '';
-                       }
-                       
-                       // Check if the storage is outdated.
-                       if (value !== null && typeof value === 'object' && value.content) {
-                               var lastEditTime = ~~elData(this._element, 'autosave-last-edit-time');
-                               if (lastEditTime * 1000 <= value.timestamp) {
-                                       // Compare the stored version with the editor content, but only use the `innerText` property
-                                       // in order to ignore differences in whitespace, e. g. caused by indentation of HTML tags.
-                                       var div1 = elCreate('div');
-                                       div1.innerHTML = this._element.value;
-                                       var div2 = elCreate('div');
-                                       div2.innerHTML = value.content;
-                                       
-                                       if (div1.innerText.trim() !== div2.innerText.trim()) {
-                                               //noinspection JSUnresolvedVariable
-                                               this._originalMessage = this._element.value;
-                                               this._restored = true;
-                                               
-                                               this._metaData = value.meta || {};
-                                               
-                                               return value.content;
-                                       }
-                               }
-                       }
-                       
-                       //noinspection JSUnresolvedVariable
-                       return this._element.value;
-               },
-               
-               /**
-                * Returns the stored meta data.
-                * 
-                * @return      {Object}
-                */
-               getMetaData: function () {
-                       return this._metaData;
-               },
-               
-               /**
-                * Enables periodical save of editor contents to local storage.
-                * 
-                * @param       {$.Redactor}    editor  redactor instance
-                */
-               watch: function(editor) {
-                       this._editor = editor;
-                       
-                       if (this._timer !== null) {
-                               throw new Error("Autosave timer is already active.");
-                       }
-                       
-                       this._timer = window.setInterval(this._saveToStorage.bind(this), _frequency * 1000);
-                       
-                       this._saveToStorage();
-                       
-                       this._isPending = false;
-               },
-               
-               /**
-                * Disables autosave handler, for use on editor destruction.
-                */
-               destroy: function () {
-                       this.clear();
-                       
-                       this._editor = null;
-                       
-                       window.clearInterval(this._timer);
-                       this._timer = null;
-                       this._isPending = false;
-               },
-               
-               /**
-                * Removed the stored message, for use after a message has been submitted.
-                */
-               clear: function () {
-                       this._metaData = {};
-                       this._lastMessage = '';
-                       
-                       try {
-                               window.localStorage.removeItem(this._key);
-                       }
-                       catch (e) {
-                               window.console.warn("Unable to remove from local storage: " + e.message);
-                       }
-               },
-               
-               /**
-                * Creates the autosave controls, used to keep or discard the restored draft.
-                */
-               createOverlay: function () {
-                       if (!this._restored) {
-                               return;
-                       }
-                       
-                       var container = elCreate('div');
-                       container.className = 'redactorAutosaveRestored active';
-                       
-                       var title = elCreate('span');
-                       title.textContent = Language.get('wcf.editor.autosave.restored');
-                       container.appendChild(title);
-                       
-                       var button = elCreate('a');
-                       button.className = 'jsTooltip';
-                       button.href = '#';
-                       button.title = Language.get('wcf.editor.autosave.keep');
-                       button.innerHTML = '<span class="icon icon16 fa-check green"></span>';
-                       button.addEventListener(WCF_CLICK_EVENT, (function (event) {
-                               event.preventDefault();
-                               
-                               this.hideOverlay();
-                       }).bind(this));
-                       container.appendChild(button);
-                       
-                       button = elCreate('a');
-                       button.className = 'jsTooltip';
-                       button.href = '#';
-                       button.title = Language.get('wcf.editor.autosave.discard');
-                       button.innerHTML = '<span class="icon icon16 fa-times red"></span>';
-                       button.addEventListener(WCF_CLICK_EVENT, (function (event) {
-                               event.preventDefault();
-                               
-                               // remove from storage
-                               this.clear();
-                               
-                               // set code
-                               var content = UiRedactorMetacode.convertFromHtml(this._editor.core.element()[0].id, this._originalMessage);
-                               this._editor.code.start(content);
-                               
-                               // set value
-                               this._editor.core.textarea().val(this._editor.clean.onSync(this._editor.$editor.html()));
-                               
-                               this.hideOverlay();
-                       }).bind(this));
-                       container.appendChild(button);
-                       
-                       this._editor.core.box()[0].appendChild(container);
-                       
-                       var callback = (function () {
-                               this._editor.core.editor()[0].removeEventListener(WCF_CLICK_EVENT, callback);
-                               
-                               this.hideOverlay();
-                       }).bind(this);
-                       this._editor.core.editor()[0].addEventListener(WCF_CLICK_EVENT, callback);
-                       
-                       this._container = container;
-               },
-               
-               /**
-                * Hides the autosave controls.
-                */
-               hideOverlay: function () {
-                       if (this._container !== null) {
-                               this._container.classList.remove('active');
-                               
-                               window.setTimeout((function () {
-                                       if (this._container !== null) {
-                                               elRemove(this._container);
-                                       }
-                                       
-                                       this._container = null;
-                                       this._originalMessage = '';
-                               }).bind(this), 1000);
-                       }
-               },
-               
-               /**
-                * Saves the current message to storage unless there was no change.
-                * 
-                * @protected
-                */
-               _saveToStorage: function() {
-                       if (!this._isActive) {
-                               if (!this._isPending) return;
-                               
-                               // save one last time before suspending
-                               this._isPending = false;
-                       }
-                       
-                       //noinspection JSUnresolvedVariable
-                       if (window.ENABLE_DEVELOPER_TOOLS && Devtools._internal_.editorAutosave() === false) {
-                               //noinspection JSUnresolvedVariable
-                               return;
-                       }
-                       
-                       var content = this._editor.code.get();
-                       if (this._editor.utils.isEmpty(content)) {
-                               content = '';
-                       }
-                       
-                       if (this._lastMessage === content) {
-                               // break if content hasn't changed
-                               return;
-                       }
-                       
-                       if (content === '') {
-                               return this.clear();
-                       }
-                       
-                       try {
-                               EventHandler.fire('com.woltlab.wcf.redactor2', 'autosaveMetaData_' + this._element.id, this._metaData);
-                               
-                               window.localStorage.setItem(this._key, JSON.stringify({
-                                       content: content,
-                                       meta: this._metaData,
-                                       timestamp: Date.now()
-                               }));
-                               
-                               this._lastMessage = content;
-                       }
-                       catch (e) {
-                               window.console.warn("Unable to write to local storage: " + e.message);
-                       }
-               },
-               
-               /**
-                * Removes stored messages older than one week.
-                * 
-                * @protected
-                */
-               _cleanup: function () {
-                       var oneWeekAgo = Date.now() - (7 * 24 * 3600 * 1000), removeKeys = [];
-                       var i, key, length, value;
-                       for (i = 0, length = window.localStorage.length; i < length; i++) {
-                               key = window.localStorage.key(i);
-                               
-                               // check if key matches our prefix
-                               if (key.indexOf(Core.getStoragePrefix()) !== 0) {
-                                       continue;
-                               }
-                               
-                               try {
-                                       value = window.localStorage.getItem(key);
-                               }
-                               catch (e) {
-                                       window.console.warn("Unable to access local storage: " + e.message);
-                               }
-                               
-                               try {
-                                       value = JSON.parse(value);
-                               }
-                               catch (e) {
-                                       value = { timestamp: 0 };
-                               }
-                               
-                               if (!value || value.timestamp < oneWeekAgo) {
-                                       removeKeys.push(key);
-                               }
-                       }
-                       
-                       for (i = 0, length = removeKeys.length; i < length; i++) {
-                               try {
-                                       window.localStorage.removeItem(removeKeys[i]);
-                               }
-                               catch (e) {
-                                       window.console.warn("Unable to remove from local storage: " + e.message);
-                               }
-                       }
-               }
-       };
-       
-       return UiRedactorAutosave;
-});
-
-/**
- * Helper class to deal with clickable block headers using the pseudo
- * `::before` element.
- * 
- * @author      Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module      WoltLabSuite/Core/Ui/Redactor/PseudoHeader
- */
-define('WoltLabSuite/Core/Ui/Redactor/PseudoHeader',[], function() {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       getHeight: function() {}
-               };
-               return Fake;
-       }
-       
-       return {
-               /**
-                * Returns the height within a click should be treated as a click
-                * within the block element's title. This method expects that the
-                * `::before` element is used and that removing the attribute
-                * `data-title` does cause the title to collapse.
-                * 
-                * @param       {Element}       element         block element
-                * @return      {int}           clickable height spanning from the top border down to the bottom of the title
-                */
-               getHeight: function (element) {
-                       var height = ~~window.getComputedStyle(element).paddingTop.replace(/px$/, '');
-                       
-                       var styles = window.getComputedStyle(element, '::before');
-                       height += ~~styles.paddingTop.replace(/px$/, '');
-                       height += ~~styles.paddingBottom.replace(/px$/, '');
-                       
-                       var titleHeight = ~~styles.height.replace(/px$/, '');
-                       if (titleHeight === 0) {
-                               // firefox returns garbage for pseudo element height
-                               // https://bugzilla.mozilla.org/show_bug.cgi?id=925694
-                               
-                               titleHeight = element.scrollHeight;
-                               element.classList.add('redactorCalcHeight');
-                               titleHeight -= element.scrollHeight;
-                               element.classList.remove('redactorCalcHeight');
-                       }
-                       
-                       height += titleHeight;
-                       
-                       return height;
-               }
-       }
-});
-
-/**
- * Manages code blocks.
- *
- * @author      Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module      WoltLabSuite/Core/Ui/Redactor/Code
- */
-define('WoltLabSuite/Core/Ui/Redactor/Code',['EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog', './PseudoHeader', 'prism/prism-meta'], function (EventHandler, EventKey, Language, StringUtil, DomUtil, UiDialog, UiRedactorPseudoHeader, PrismMeta) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _bbcodeCode: function() {},
-                       _observeLoad: function() {},
-                       _edit: function() {},
-                       _setTitle: function() {},
-                       _delete: function() {},
-                       _dialogSetup: function() {},
-                       _dialogSubmit: function() {}
-               };
-               return Fake;
-       }
-       
-       var _headerHeight = 0;
-       
-       /**
-        * @param       {Object}        editor  editor instance
-        * @constructor
-        */
-       function UiRedactorCode(editor) { this.init(editor); }
-       UiRedactorCode.prototype = {
-               /**
-                * Initializes the source code management.
-                * 
-                * @param       {Object}        editor  editor instance
-                */
-               init: function(editor) {
-                       this._editor = editor;
-                       this._elementId = this._editor.$element[0].id;
-                       this._pre = null;
-                       
-                       EventHandler.add('com.woltlab.wcf.redactor2', 'bbcode_code_' + this._elementId, this._bbcodeCode.bind(this));
-                       EventHandler.add('com.woltlab.wcf.redactor2', 'observe_load_' + this._elementId, this._observeLoad.bind(this));
-                       
-                       // support for active button marking
-                       this._editor.opts.activeButtonsStates.pre = 'code';
-                       
-                       // static bind to ensure that removing works
-                       this._callbackEdit = this._edit.bind(this);
-                       
-                       // bind listeners on init
-                       this._observeLoad();
-               },
-               
-               /**
-                * Intercepts the insertion of `[code]` tags and uses a native `<pre>` instead.
-                * 
-                * @param       {Object}        data    event data
-                * @protected
-                */
-               _bbcodeCode: function(data) {
-                       data.cancel = true;
-                       
-                       var pre = this._editor.selection.block();
-                       if (pre && pre.nodeName === 'PRE' && pre.classList.contains('woltlabHtml')) {
-                               return;
-                       }
-                       
-                       this._editor.button.toggle({}, 'pre', 'func', 'block.format');
-                       
-                       pre = this._editor.selection.block();
-                       if (pre && pre.nodeName === 'PRE' && !pre.classList.contains('woltlabHtml')) {
-                               if (pre.childElementCount === 1 && pre.children[0].nodeName === 'BR') {
-                                       // drop superfluous linebreak
-                                       pre.removeChild(pre.children[0]);
-                               }
-                               
-                               this._setTitle(pre);
-                               
-                               pre.addEventListener(WCF_CLICK_EVENT, this._callbackEdit);
-                               
-                               // work-around for Safari
-                               this._editor.caret.end(pre);
-                       }
-               },
-               
-               /**
-                * Binds event listeners and sets quote title on both editor
-                * initialization and when switching back from code view.
-                * 
-                * @protected
-                */
-               _observeLoad: function() {
-                       elBySelAll('pre:not(.woltlabHtml)', this._editor.$editor[0], (function(pre) {
-                               pre.addEventListener('mousedown', this._callbackEdit);
-                               this._setTitle(pre);
-                       }).bind(this));
-               },
-               
-               /**
-                * Opens the dialog overlay to edit the code's properties.
-                * 
-                * @param       {Event}         event           event object
-                * @protected
-                */
-               _edit: function(event) {
-                       var pre = event.currentTarget;
-                       
-                       if (_headerHeight === 0) {
-                               _headerHeight = UiRedactorPseudoHeader.getHeight(pre);
-                       }
-                       
-                       // check if the click hit the header
-                       var offset = DomUtil.offset(pre);
-                       if (event.pageY > offset.top && event.pageY < (offset.top + _headerHeight)) {
-                               event.preventDefault();
-                               
-                               this._editor.selection.save();
-                               this._pre = pre;
-                               
-                               UiDialog.open(this);
-                       }
-               },
-               
-               /**
-                * Saves the changes to the code's properties.
-                * 
-                * @protected
-                */
-               _dialogSubmit: function() {
-                       var id = 'redactor-code-' + this._elementId;
-                       
-                       ['file', 'highlighter', 'line'].forEach((function (attr) {
-                               elData(this._pre, attr, elById(id + '-' + attr).value);
-                       }).bind(this));
-                       
-                       this._setTitle(this._pre);
-                       this._editor.caret.after(this._pre);
-                       
-                       UiDialog.close(this);
-               },
-               
-               /**
-                * Sets or updates the code's header title.
-                * 
-                * @param       {Element}       pre     code element
-                * @protected
-                */
-               _setTitle: function(pre) {
-                       var file = elData(pre, 'file'),
-                           highlighter = elData(pre, 'highlighter');
-                       
-                       //noinspection JSUnresolvedVariable
-                       highlighter = (this._editor.opts.woltlab.highlighters.hasOwnProperty(highlighter)) ? this._editor.opts.woltlab.highlighters[highlighter] : '';
-                       
-                       var title = Language.get('wcf.editor.code.title', {
-                               file: file,
-                               highlighter: highlighter
-                       });
-                       
-                       if (elData(pre, 'title') !== title) {
-                               elData(pre, 'title', title);
-                       }
-               },
-               
-               _delete: function (event) {
-                       event.preventDefault();
-                       
-                       var caretEnd = this._pre.nextElementSibling || this._pre.previousElementSibling;
-                       if (caretEnd === null && this._pre.parentNode !== this._editor.core.editor()[0]) {
-                               caretEnd = this._pre.parentNode;
-                       }
-                       
-                       if (caretEnd === null) {
-                               this._editor.code.set('');
-                               this._editor.focus.end();
-                       }
-                       else {
-                               elRemove(this._pre);
-                               this._editor.caret.end(caretEnd);
-                       }
-                       
-                       UiDialog.close(this);
-               },
-               
-               _dialogSetup: function() {
-                       var id = 'redactor-code-' + this._elementId,
-                           idButtonDelete = id + '-button-delete',
-                           idButtonSave = id + '-button-save',
-                           idFile = id + '-file',
-                           idHighlighter = id + '-highlighter',
-                           idLine = id + '-line';
-                       
-                       return {
-                               id: id,
-                               options: {
-                                       onClose: (function () {
-                                               this._editor.selection.restore();
-                                               
-                                               UiDialog.destroy(this);
-                                       }).bind(this),
-                                       
-                                       onSetup: (function() {
-                                               elById(idButtonDelete).addEventListener(WCF_CLICK_EVENT, this._delete.bind(this));
-                                               
-                                               // set highlighters
-                                               var highlighters = '<option value="">' + Language.get('wcf.editor.code.highlighter.detect') + '</option>';
-                                               highlighters += '<option value="plain">' + Language.get('wcf.editor.code.highlighter.plain') + '</option>';
-                                               
-                                               //noinspection JSUnresolvedVariable
-                                               var values = this._editor.opts.woltlab.highlighters.map(function (highlighter) {
-                                                       return [highlighter, PrismMeta[highlighter].title];
-                                               });
-                                               
-                                               // sort by label
-                                               values.sort(function(a, b) {
-                                                       if (a[1] < b[1]) {
-                                                               return  -1;
-                                                       }
-                                                       else if (a[1] > b[1]) {
-                                                               return 1;
-                                                       }
-                                                       
-                                                       return 0;
-                                               });
-                                               
-                                               values.forEach((function(value) {
-                                                       highlighters += '<option value="' + value[0] + '">' + StringUtil.escapeHTML(value[1]) + '</option>';
-                                               }).bind(this));
-                                               
-                                               elById(idHighlighter).innerHTML = highlighters;
-                                       }).bind(this),
-                                       
-                                       onShow: (function() {
-                                               elById(idHighlighter).value = elData(this._pre, 'highlighter');
-                                               var line = elData(this._pre, 'line');
-                                               elById(idLine).value = (line === '') ? 1 : ~~line;
-                                               elById(idFile).value = elData(this._pre, 'file');
-                                       }).bind(this),
-                                       
-                                       title: Language.get('wcf.editor.code.edit')
-                               },
-                               source: '<div class="section">'
-                                       + '<dl>'
-                                               + '<dt><label for="' + idHighlighter + '">' + Language.get('wcf.editor.code.highlighter') + '</label></dt>'
-                                               + '<dd>'
-                                                       + '<select id="' + idHighlighter + '"></select>'
-                                                       + '<small>' + Language.get('wcf.editor.code.highlighter.description') + '</small>'
-                                               + '</dd>'
-                                       + '</dl>'
-                                       + '<dl>'
-                                               + '<dt><label for="' + idLine + '">' + Language.get('wcf.editor.code.line') + '</label></dt>'
-                                               + '<dd>'
-                                                       + '<input type="number" id="' + idLine + '" min="0" value="1" class="long" data-dialog-submit-on-enter="true">'
-                                                       + '<small>' + Language.get('wcf.editor.code.line.description') + '</small>'
-                                               + '</dd>'
-                                       + '</dl>'
-                                       + '<dl>'
-                                               + '<dt><label for="' + idFile + '">' + Language.get('wcf.editor.code.file') + '</label></dt>'
-                                               + '<dd>'
-                                                       + '<input type="text" id="' + idFile + '" class="long" data-dialog-submit-on-enter="true">'
-                                                       + '<small>' + Language.get('wcf.editor.code.file.description') + '</small>'
-                                               + '</dd>'
-                                       + '</dl>'
-                               + '</div>'
-                               + '<div class="formSubmit">'
-                                       + '<button id="' + idButtonSave + '" class="buttonPrimary" data-type="submit">' + Language.get('wcf.global.button.save') + '</button>'
-                                       + '<button id="' + idButtonDelete + '">' + Language.get('wcf.global.button.delete') + '</button>'
-                               + '</div>'
-                       };
-               }
-       };
-       
-       return UiRedactorCode;
-});
-
-/**
- * Provides helper methods to add and remove format elements. These methods should in
- * theory work with non-editor elements but has not been tested and any usage outside
- * the editor is not recommended.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Redactor/Format
- */
-define('WoltLabSuite/Core/Ui/Redactor/Format',['Dom/Util'], function(DomUtil) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       format: function() {},
-                       removeFormat: function() {},
-                       _handleParentNodes: function() {},
-                       _getLastMatchingParent: function() {},
-                       _isBoundaryElement: function() {},
-                       _getSelectionMarker: function() {}
-               };
-               return Fake;
-       }
-       
-       var _isValidSelection = function(editorElement) {
-               var element = window.getSelection().anchorNode;
-               while (element) {
-                       if (element === editorElement) {
-                               return true;
-                       }
-                       
-                       element = element.parentNode;
-               }
-               
-               return false;
-       };
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Redactor/Format
-        */
-       return {
-               /**
-                * Applies format elements to the selected text.
-                * 
-                * @param       {Element}       editorElement   editor element
-                * @param       {string}        property        CSS property name
-                * @param       {string}        value           CSS property value
-                */
-               format: function(editorElement, property, value) {
-                       var selection = window.getSelection();
-                       if (!selection.rangeCount) {
-                               // no active selection
-                               return;
-                       }
-                       
-                       if (!_isValidSelection(editorElement)) {
-                               console.error("Invalid selection, range exists outside of the editor:", selection.anchorNode);
-                               return;
-                       }
-                       
-                       var range = selection.getRangeAt(0);
-                       var markerStart = null, markerEnd = null, tmpElement = null;
-                       if (range.collapsed) {
-                               tmpElement = elCreate('strike');
-                               tmpElement.textContent = '\u200B';
-                               range.insertNode(tmpElement);
-                               
-                               range = document.createRange();
-                               range.selectNodeContents(tmpElement);
-                               
-                               selection.removeAllRanges();
-                               selection.addRange(range);
-                       }
-                       else {
-                               // removing existing format causes the selection to vanish,
-                               // these markers are used to restore it afterwards
-                               markerStart = elCreate('mark');
-                               markerEnd = elCreate('mark');
-                               
-                               var tmpRange = range.cloneRange();
-                               tmpRange.collapse(true);
-                               tmpRange.insertNode(markerStart);
-                               
-                               tmpRange = range.cloneRange();
-                               tmpRange.collapse(false);
-                               tmpRange.insertNode(markerEnd);
-                               
-                               range = document.createRange();
-                               range.setStartAfter(markerStart);
-                               range.setEndBefore(markerEnd);
-                               
-                               selection.removeAllRanges();
-                               selection.addRange(range);
-                               
-                               // remove existing format before applying new one
-                               this.removeFormat(editorElement, property);
-                               
-                               range = document.createRange();
-                               range.setStartAfter(markerStart);
-                               range.setEndBefore(markerEnd);
-                               
-                               selection.removeAllRanges();
-                               selection.addRange(range);
-                       }
-                       
-                       var selectionMarker = ['strike', 'strikethrough'];
-                       if (tmpElement === null) {
-                               selectionMarker = this._getSelectionMarker(editorElement, selection);
-                               
-                               document.execCommand(selectionMarker[1]);
-                       }
-                       
-                       var elements = elBySelAll(selectionMarker[0], editorElement), formatElement, selectElements = [], strike;
-                       for (var i = 0, length = elements.length; i < length; i++) {
-                               strike = elements[i];
-                               
-                               formatElement = elCreate('span');
-                               // we're bypassing `style.setPropertyValue()` on purpose here,
-                               // as it prevents browsers from mangling the value
-                               elAttr(formatElement, 'style', property + ': ' + value);
-                               
-                               DomUtil.replaceElement(strike, formatElement);
-                               selectElements.push(formatElement);
-                       }
-                       
-                       var count = selectElements.length;
-                       if (count) {
-                               var firstSelectedElement = selectElements[0];
-                               var lastSelectedElement = selectElements[count - 1];
-                               
-                               // check if parent is of the same format
-                               // and contains only the selected nodes
-                               if (tmpElement === null && (firstSelectedElement.parentNode === lastSelectedElement.parentNode)) {
-                                       var parent = firstSelectedElement.parentNode;
-                                       if (parent.nodeName === 'SPAN' && parent.style.getPropertyValue(property) !== '') {
-                                               if (this._isBoundaryElement(firstSelectedElement, parent, 'previous') && this._isBoundaryElement(lastSelectedElement, parent, 'next')) {
-                                                       DomUtil.unwrapChildNodes(parent);
-                                               }
-                                       }
-                               }
-                               
-                               range = document.createRange();
-                               range.setStart(firstSelectedElement, 0);
-                               range.setEnd(lastSelectedElement, lastSelectedElement.childNodes.length);
-                               
-                               selection.removeAllRanges();
-                               selection.addRange(range);
-                       }
-                       
-                       if (markerStart !== null) {
-                               elRemove(markerStart);
-                               elRemove(markerEnd);
-                       }
-               },
-               
-               /**
-                * Removes a format element from the current selection.
-                * 
-                * The removal uses a few techniques to remove the target element(s) without harming
-                * nesting nor any other formatting present. The steps taken are described below:
-                * 
-                * 1. The browser will wrap all parts of the selection into <strike> tags
-                * 
-                *      This isn't the most efficient way to isolate each selected node, but is the
-                *      most reliable way to accomplish this because the browser will insert them
-                *      exactly where the range spans without harming the node nesting.
-                *      
-                *      Basically it is a trade-off between efficiency and reliability, the performance
-                *      is still excellent but could be better at the expense of an increased complexity,
-                *      which simply doesn't exactly pay off.
-                * 
-                * 2. Iterate over each inserted <strike> and isolate all relevant ancestors
-                * 
-                *      Format tags can appear both as a child of the <strike> as well as once or multiple
-                *      times as an ancestor.
-                *      
-                *      It uses ranges to select the contents before the <strike> element up to the start
-                *      of the last matching ancestor and cuts out the nodes. The browser will ensure that
-                *      the resulting fragment will include all relevant ancestors that were present before.
-                *      
-                *      The example below will use the fictional <bar> elements as the tag to remove, the
-                *      pipe ("|") is used to denote the outer node boundaries.
-                *      
-                *      Before:
-                *      |<bar>This is <foo>a <strike>simple <bar>example</bar></strike></foo></bar>|
-                *      After:
-                *      |<bar>This is <foo>a </foo></bar>|<bar><foo>simple <bar>example</bar></strike></foo></bar>|
-                *      
-                *      As a result we can now remove <bar> both inside the <strike> element as well as
-                *      the outer <bar> without harming the effect of <bar> for the preceding siblings.
-                *      
-                *      This process is repeated for siblings appearing after the <strike> element too, it
-                *      works as described above but flipped. This is an expensive operation and will only
-                *      take place if there are any matching ancestors that need to be considered.
-                *      
-                *      Inspired by http://stackoverflow.com/a/12899461
-                * 
-                * 3. Remove all matching ancestors, child elements and last the <strike> element itself
-                * 
-                *      Depending on the amount of nested matching nodes, this process will move a lot of
-                *      nodes around. Removing the <bar> element will require all its child nodes to be moved
-                *      in front of <bar>, they will actually become a sibling of <bar>. Afterwards the
-                *      (now empty) <bar> element can be safely removed without losing any nodes.
-                * 
-                * 
-                * One last hint: This method will not check if the selection at some point contains at
-                * least one target element, it assumes that the user will not take any action that invokes
-                * this method for no reason (unless they want to waste CPU cycles, in that case they're
-                * welcome).
-                * 
-                * This is especially important for developers as this method shouldn't be called for
-                * no good reason. Even though it is super fast, it still comes with expensive DOM operations
-                * and especially low-end devices (such as cheap smartphones) might not exactly like executing
-                * this method on large documents.
-                * 
-                * If you fell the need to invoke this method anyway, go ahead. I'm a comment, not a cop.
-                * 
-                * @param       {Element}       editorElement   editor element
-                * @param       {string}        property        CSS property that should be removed
-                */
-               removeFormat: function(editorElement, property) {
-                       var selection = window.getSelection();
-                       if (!selection.rangeCount) {
-                               return;
-                       }
-                       else if (!_isValidSelection(editorElement)) {
-                               console.error("Invalid selection, range exists outside of the editor:", selection.anchorNode);
-                               return;
-                       }
-                       
-                       // Removing a span from an empty selection in an empty line containing a `<br>` causes a selection
-                       // shift where the caret is moved into the span again. Unlike inline changes to the formatting, any
-                       // removal of the format in an empty line should remove it from its entirely, instead of just around
-                       // the caret position.
-                       var range = selection.getRangeAt(0);
-                       var helperTextNode = null;
-                       if (range.collapsed) {
-                               var container = range.startContainer;
-                               var tree = [container];
-                               while (true) {
-                                       var parent = container.parentNode;
-                                       if (parent === editorElement || parent.nodeName === 'TD') {
-                                               break;
-                                       }
-                                       
-                                       container = parent;
-                                       tree.push(container);
-                               }
-                               
-                               if (this._isEmpty(container.innerHTML)) {
-                                       var marker = document.createElement('woltlab-format-marker');
-                                       range.insertNode(marker);
-                                       
-                                       // Find the offending span and remove it entirely.
-                                       tree.forEach(function (element) {
-                                               if (element.nodeName === 'SPAN') {
-                                                       if (element.style.getPropertyValue(property)) {
-                                                               DomUtil.unwrapChildNodes(element);
-                                                       }
-                                               }
-                                       });
-                                       
-                                       // Firefox messes up the selection if the ancestor element was removed and there is
-                                       // an adjacent `<br>` present. Instead of keeping the caret in front of the <br>, it
-                                       // is implicitly moved behind it.
-                                       range = document.createRange();
-                                       range.selectNode(marker);
-                                       range.collapse(true);
-                                       
-                                       selection.removeAllRanges();
-                                       selection.addRange(range);
-                                       
-                                       elRemove(marker);
-                                       
-                                       return;
-                               }
-                               
-                               // Fill up the range with a zero length whitespace to give the browser
-                               // something to strike through. If the range is completely empty, the
-                               // "strike" is remembered by the browser, but not actually inserted into
-                               // the DOM, causing the next keystroke to magically insert it.
-                               helperTextNode = document.createTextNode('\u200B');
-                               range.insertNode(helperTextNode);
-                       }
-                       
-                       var strikeElements = elByTag('strike', editorElement);
-                       
-                       // remove any <strike> element first, all though there shouldn't be any at all
-                       while (strikeElements.length) {
-                               DomUtil.unwrapChildNodes(strikeElements[0]);
-                       }
-                       
-                       var selectionMarker = this._getSelectionMarker(editorElement, window.getSelection());
-                       
-                       document.execCommand(selectionMarker[1]);
-                       if (selectionMarker[0] !== 'strike') {
-                               strikeElements = elByTag(selectionMarker[0], editorElement);
-                       }
-                       
-                       var lastMatchingParent, strikeElement;
-                       while (strikeElements.length) {
-                               strikeElement = strikeElements[0];
-                               lastMatchingParent = this._getLastMatchingParent(strikeElement, editorElement, property);
-                               
-                               if (lastMatchingParent !== null) {
-                                       this._handleParentNodes(strikeElement, lastMatchingParent, property);
-                               }
-                               
-                               // remove offending elements from child nodes
-                               elBySelAll('span', strikeElement, function (span) {
-                                       if (span.style.getPropertyValue(property)) {
-                                               DomUtil.unwrapChildNodes(span);
-                                       }
-                               });
-                               
-                               // remove strike element itself
-                               DomUtil.unwrapChildNodes(strikeElement);
-                       }
-                       
-                       // search for tags that are still floating around, but are completely empty
-                       elBySelAll('span', editorElement, function (element) {
-                               if (element.parentNode && !element.textContent.length && element.style.getPropertyValue(property) !== '') {
-                                       if (element.childElementCount === 1 && element.children[0].nodeName === 'MARK') {
-                                               element.parentNode.insertBefore(element.children[0], element);
-                                       }
-                                       
-                                       if (element.childElementCount === 0) {
-                                               elRemove(element);
-                                       }
-                               }
-                       });
-                       
-                       if (helperTextNode !== null) {
-                               window.jQuery(editorElement).redactor('caret.after', range.parentNode);
-                               elRemove(helperTextNode);
-                       }
-               },
-               
-               /**
-                * Slices relevant parent nodes and removes matching ancestors.
-                * 
-                * @param       {Element}       strikeElement           strike element representing the text selection
-                * @param       {Element}       lastMatchingParent      last matching ancestor element
-                * @param       {string}        property                CSS property that should be removed
-                * @protected
-                */
-               _handleParentNodes: function(strikeElement, lastMatchingParent, property) {
-                       var range;
-                       
-                       // selection does not begin at parent node start, slice all relevant parent
-                       // nodes to ensure that selection is then at the beginning while preserving
-                       // all proper ancestor elements
-                       // 
-                       // before: (the pipe represents the node boundary)
-                       // |otherContent <-- selection -->
-                       // after:
-                       // |otherContent| |<-- selection -->
-                       if (!DomUtil.isAtNodeStart(strikeElement, lastMatchingParent)) {
-                               range = document.createRange();
-                               range.setStartBefore(lastMatchingParent);
-                               range.setEndBefore(strikeElement);
-                               
-                               var fragment = range.extractContents();
-                               lastMatchingParent.parentNode.insertBefore(fragment, lastMatchingParent);
-                       }
-                       
-                       // selection does not end at parent node end, slice all relevant parent nodes
-                       // to ensure that selection is then at the end while preserving all proper
-                       // ancestor elements
-                       // 
-                       // before: (the pipe represents the node boundary)
-                       // <-- selection --> otherContent|
-                       // after:
-                       // <-- selection -->| |otherContent|
-                       if (!DomUtil.isAtNodeEnd(strikeElement, lastMatchingParent)) {
-                               range = document.createRange();
-                               range.setStartAfter(strikeElement);
-                               range.setEndAfter(lastMatchingParent);
-                               
-                               fragment = range.extractContents();
-                               lastMatchingParent.parentNode.insertBefore(fragment, lastMatchingParent.nextSibling);
-                       }
-                       
-                       // the strike element is now some kind of isolated, meaning we can now safely
-                       // remove all offending parent nodes without influencing formatting of any content
-                       // before or after the element
-                       elBySelAll('span', lastMatchingParent, function (span) {
-                               if (span.style.getPropertyValue(property)) {
-                                       DomUtil.unwrapChildNodes(span);
-                               }
-                       });
-                       
-                       // finally remove the parent itself
-                       DomUtil.unwrapChildNodes(lastMatchingParent);
-               },
-               
-               /**
-                * Finds the last matching ancestor until it reaches the editor element.
-                * 
-                * @param       {Element}               strikeElement   strike element representing the text selection
-                * @param       {Element}               editorElement   editor element
-                * @param       {string}                property        CSS property that should be removed
-                * @returns     {(Element|null)}        last matching ancestor element or null if there is none
-                * @protected
-                */
-               _getLastMatchingParent: function(strikeElement, editorElement, property) {
-                       var parent = strikeElement.parentNode, match = null;
-                       while (parent !== editorElement) {
-                               if (parent.nodeName === 'SPAN' && parent.style.getPropertyValue(property) !== '') {
-                                       match = parent;
-                               }
-                               
-                               parent = parent.parentNode;
-                       }
-                       
-                       return match;
-               },
-               
-               /**
-                * Returns true if provided element is the first or last element
-                * of its parent, ignoring empty text nodes appearing between the
-                * element and the boundary.
-                * 
-                * @param       {Element}       element         target element
-                * @param       {Element}       parent          parent element
-                * @param       {string}        type            traversal direction, can be either `next` or `previous`
-                * @return      {boolean}       true if element is the non-empty boundary element
-                * @protected
-                */
-               _isBoundaryElement: function (element, parent, type) {
-                       var node = element;
-                       while (node = node[type + 'Sibling']) {
-                               if (node.nodeType !== Node.TEXT_NODE || node.textContent.replace(/\u200B/, '') !== '') {
-                                       return false;
-                               }
-                       }
-                       
-                       return true;
-               },
-               
-               /**
-                * Returns a custom selection marker element, can be either `strike`, `sub` or `sup`. Using other kind
-                * of formattings is not possible due to the inconsistent behavior across browsers.
-                * 
-                * @param       {Element}       editorElement   editor element
-                * @param       {Selection}     selection       selection object
-                * @return      {string[]}      tag name and command name
-                * @protected
-                */
-               _getSelectionMarker: function (editorElement, selection) {
-                       var hasNode, node, tag, tags = ['DEL', 'SUB', 'SUP'];
-                       for (var i = 0, length = tags.length; i < length; i++) {
-                               tag = tags[i];
-                               
-                               node = elClosest(selection.anchorNode);
-                               hasNode = (elBySel(tag.toLowerCase(), node) !== null);
-                               
-                               if (!hasNode) {
-                                       while (node && node !== editorElement) {
-                                               if (node.nodeName === tag) {
-                                                       hasNode = true;
-                                                       break;
-                                               }
-                                               
-                                               node = node.parentNode;
-                                       }
-                               }
-                               
-                               if (hasNode) {
-                                       tag = undefined;
-                               }
-                               else {
-                                       break;
-                               }
-                       }
-                       
-                       if (tag === 'DEL' || tag === undefined) {
-                               return ['strike', 'strikethrough'];
-                       }
-                       
-                       return [tag.toLowerCase(), tag.toLowerCase() + 'script'];
-               },
-               
-               /**
-                * Slightly modified version of Redactor's `utils.isEmpty()`.
-                * 
-                * @param {string} html
-                * @returns {boolean}
-                * @protected
-                */
-               _isEmpty: function(html) {
-                       html = html.replace(/[\u200B-\u200D\uFEFF]/g, '');
-                       html = html.replace(/&nbsp;/gi, '');
-                       html = html.replace(/<\/?br\s?\/?>/g, '');
-                       html = html.replace(/\s/g, '');
-                       html = html.replace(/^<p>[^\W\w\D\d]*?<\/p>$/i, '');
-                       html = html.replace(/<iframe(.*?[^>])>$/i, 'iframe');
-                       html = html.replace(/<source(.*?[^>])>$/i, 'source');
-                       
-                       // remove empty tags
-                       html = html.replace(/<[^\/>][^>]*><\/[^>]+>/gi, '');
-                       html = html.replace(/<[^\/>][^>]*><\/[^>]+>/gi, '');
-                       
-                       return html.trim() === '';
-               }
-       };
-});
-
-/**
- * Manages html code blocks.
- * 
- * @author      Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module      WoltLabSuite/Core/Ui/Redactor/Html
- */
-define('WoltLabSuite/Core/Ui/Redactor/Html',['EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog', './PseudoHeader'], function (EventHandler, EventKey, Language, StringUtil, DomUtil, UiDialog, UiRedactorPseudoHeader) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _bbcodeCode: function() {},
-                       _observeLoad: function() {},
-                       _edit: function() {},
-                       _save: function() {},
-                       _setTitle: function() {},
-                       _delete: function() {},
-                       _dialogSetup: function() {}
-               };
-               return Fake;
-       }
-       
-       var _headerHeight = 0;
-       
-       /**
-        * @param       {Object}        editor  editor instance
-        * @constructor
-        */
-       function UiRedactorHtml(editor) { this.init(editor); }
-       UiRedactorHtml.prototype = {
-               /**
-                * Initializes the source code management.
-                *
-                * @param       {Object}        editor  editor instance
-                */
-               init: function(editor) {
-                       this._editor = editor;
-                       this._elementId = this._editor.$element[0].id;
-                       this._pre = null;
-                       
-                       EventHandler.add('com.woltlab.wcf.redactor2', 'bbcode_woltlabHtml_' + this._elementId, this._bbcodeCode.bind(this));
-                       EventHandler.add('com.woltlab.wcf.redactor2', 'observe_load_' + this._elementId, this._observeLoad.bind(this));
-                       
-                       // support for active button marking
-                       this._editor.opts.activeButtonsStates['woltlab-html'] = 'woltlabHtml';
-                       
-                       // static bind to ensure that removing works
-                       this._callbackEdit = this._edit.bind(this);
-                       
-                       // bind listeners on init
-                       this._observeLoad();
-               },
-               
-               /**
-                * Intercepts the insertion of `[woltlabHtml]` tags and uses a native `<pre>` instead.
-                *
-                * @param       {Object}        data    event data
-                * @protected
-                */
-               _bbcodeCode: function(data) {
-                       data.cancel = true;
-                       
-                       var pre = this._editor.selection.block();
-                       if (pre && pre.nodeName === 'PRE' && !pre.classList.contains('woltlabHtml')) {
-                               return;
-                       }
-                       
-                       this._editor.button.toggle({}, 'pre', 'func', 'block.format');
-                       
-                       pre = this._editor.selection.block();
-                       if (pre && pre.nodeName === 'PRE') {
-                               pre.classList.add('woltlabHtml');
-                               
-                               if (pre.childElementCount === 1 && pre.children[0].nodeName === 'BR') {
-                                       // drop superfluous linebreak
-                                       pre.removeChild(pre.children[0]);
-                               }
-                               
-                               this._setTitle(pre);
-                               
-                               pre.addEventListener(WCF_CLICK_EVENT, this._callbackEdit);
-                               
-                               // work-around for Safari
-                               this._editor.caret.end(pre);
-                       }
-               },
-               
-               /**
-                * Binds event listeners and sets quote title on both editor
-                * initialization and when switching back from code view.
-                *
-                * @protected
-                */
-               _observeLoad: function() {
-                       elBySelAll('pre.woltlabHtml', this._editor.$editor[0], (function(pre) {
-                               pre.addEventListener('mousedown', this._callbackEdit);
-                               this._setTitle(pre);
-                       }).bind(this));
-               },
-               
-               /**
-                * Opens the dialog overlay to edit the code's properties.
-                *
-                * @param       {Event}         event           event object
-                * @protected
-                */
-               _edit: function(event) {
-                       var pre = event.currentTarget;
-                       
-                       if (_headerHeight === 0) {
-                               _headerHeight = UiRedactorPseudoHeader.getHeight(pre);
-                       }
-                       
-                       // check if the click hit the header
-                       var offset = DomUtil.offset(pre);
-                       if (event.pageY > offset.top && event.pageY < (offset.top + _headerHeight)) {
-                               event.preventDefault();
-                               
-                               this._editor.selection.save();
-                               this._pre = pre;
-                               
-                               console.warn("should edit");
-                       }
-               },
-               
-               /**
-                * Sets or updates the code's header title.
-                *
-                * @param       {Element}       pre     code element
-                * @protected
-                */
-               _setTitle: function(pre) {
-                       ['title', 'description'].forEach(function(title) {
-                               var phrase = Language.get('wcf.editor.html.' + title);
-                               
-                               if (elData(pre, title) !== phrase) {
-                                       elData(pre, title, phrase);
-                               }
-                       });
-               },
-               
-               _delete: function (event) {
-                       console.warn("should delete");
-                       event.preventDefault();
-                       
-                       var caretEnd = this._pre.nextElementSibling || this._pre.previousElementSibling;
-                       if (caretEnd === null && this._pre.parentNode !== this._editor.core.editor()[0]) {
-                               caretEnd = this._pre.parentNode;
-                       }
-                       
-                       if (caretEnd === null) {
-                               this._editor.code.set('');
-                               this._editor.focus.end();
-                       }
-                       else {
-                               elRemove(this._pre);
-                               this._editor.caret.end(caretEnd);
-                       }
-                       
-                       UiDialog.close(this);
-               }
-       };
-       
-       return UiRedactorHtml;
-});
-define('WoltLabSuite/Core/Ui/Redactor/Link',['Core', 'EventKey', 'Language', 'Ui/Dialog'], function(Core, EventKey, Language, UiDialog) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       showDialog: function() {},
-                       _submit: function() {},
-                       _dialogSetup: function() {}
-               };
-               return Fake;
-       }
-       
-       var _boundListener = false;
-       var _callback = null;
-       
-       return {
-               showDialog: function(options) {
-                       UiDialog.open(this);
-                       
-                       UiDialog.setTitle(this, Language.get('wcf.editor.link.' + (options.insert ? 'add' : 'edit')));
-                       
-                       var submitButton = elById('redactor-modal-button-action');
-                       submitButton.textContent = Language.get('wcf.global.button.' + (options.insert ? 'insert' : 'save'));
-                       
-                       _callback = options.submitCallback;
-                       
-                       if (!_boundListener) {
-                               _boundListener = true;
-                               
-                               submitButton.addEventListener(WCF_CLICK_EVENT, this._submit.bind(this));
-                       }
-               },
-               
-               _submit: function() {
-                       if (_callback()) {
-                               UiDialog.close(this);
-                       }
-                       else {
-                               var url = elById('redactor-link-url');
-                               elInnerError(url, Language.get((url.value.trim() === '' ? 'wcf.global.form.error.empty' : 'wcf.editor.link.error.invalid')));
-                       }
-               },
-               
-               _dialogSetup: function() {
-                       return {
-                               id: 'redactorDialogLink',
-                               options: {
-                                       onClose: function() {
-                                               var url = elById('redactor-link-url');
-                                               var small = (url.nextElementSibling && url.nextElementSibling.nodeName === 'SMALL') ? url.nextElementSibling : null;
-                                               if (small !== null) {
-                                                       elRemove(small);
-                                               }
-                                       },
-                                       onSetup: function (content) {
-                                               var submitButton = elBySel('.formSubmit > .buttonPrimary', content);
-                                               
-                                               if (submitButton !== null) {
-                                                       elBySelAll('input[type="url"], input[type="text"]', content, function (input) {
-                                                               input.addEventListener('keyup', function (event) {
-                                                                       if (EventKey.Enter(event)) {
-                                                                               Core.triggerEvent(submitButton, 'click');
-                                                                       }
-                                                               });
-                                                       });
-                                               }
-                                       },
-                                       onShow: function () {
-                                               elById('redactor-link-url').focus();
-                                       }
-                               },
-                               source: '<dl>'
-                                               + '<dt><label for="redactor-link-url">' + Language.get('wcf.editor.link.url') + '</label></dt>'
-                                               + '<dd><input type="url" id="redactor-link-url" class="long"></dd>'
-                                       + '</dl>'
-                                       + '<dl>'
-                                               + '<dt><label for="redactor-link-url-text">' + Language.get('wcf.editor.link.text') + '</label></dt>'
-                                               + '<dd><input type="text" id="redactor-link-url-text" class="long"></dd>'
-                                       + '</dl>'
-                                       + '<div class="formSubmit">'
-                                               + '<button id="redactor-modal-button-action" class="buttonPrimary"></button>'
-                                       + '</div>'
-                       };
-               }
-       };
-});
-
-define('WoltLabSuite/Core/Ui/Redactor/Mention',['Ajax', 'Environment', 'StringUtil', 'Ui/CloseOverlay'], function(Ajax, Environment, StringUtil, UiCloseOverlay) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _keyDown: function() {},
-                       _keyUp: function() {},
-                       _getTextLineInFrontOfCaret: function() {},
-                       _getDropdownMenuPosition: function() {},
-                       _setUsername: function() {},
-                       _selectMention: function() {},
-                       _updateDropdownPosition: function() {},
-                       _selectItem: function() {},
-                       _hideDropdown: function() {},
-                       _ajaxSetup: function() {},
-                       _ajaxSuccess: function() {}
-               };
-               return Fake;
-       }
-       
-       var _dropdownContainer = null;
-       
-       function UiRedactorMention(redactor) { this.init(redactor); }
-       UiRedactorMention.prototype = {
-               init: function(redactor) {
-                       this._active = false;
-                       this._dropdownActive = false;
-                       this._dropdownMenu = null;
-                       this._itemIndex = 0;
-                       this._lineHeight = null;
-                       this._mentionStart = '';
-                       this._redactor = redactor;
-                       this._timer = null;
-                       
-                       redactor.WoltLabEvent.register('keydown', this._keyDown.bind(this));
-                       redactor.WoltLabEvent.register('keyup', this._keyUp.bind(this));
-                       
-                       UiCloseOverlay.add('UiRedactorMention-' + redactor.core.element()[0].id, this._hideDropdown.bind(this));
-               },
-               
-               _keyDown: function(data) {
-                       if (!this._dropdownActive) {
-                               return;
-                       }
-                       
-                       /** @var Event event */
-                       var event = data.event;
-                       
-                       switch (event.which) {
-                               // enter
-                               case 13:
-                                       this._setUsername(null, this._dropdownMenu.children[this._itemIndex].children[0]);
-                                       break;
-                               
-                               // arrow up
-                               case 38:
-                                       this._selectItem(-1);
-                                       break;
-                               
-                               // arrow down
-                               case 40:
-                                       this._selectItem(1);
-                                       break;
-                               
-                               default:
-                                       this._hideDropdown();
-                                       return;
-                                       break;
-                       }
-                       
-                       event.preventDefault();
-                       data.cancel = true;
-               },
-               
-               _keyUp: function(data) {
-                       /** @var Event event */
-                       var event = data.event;
-                       
-                       // ignore return key
-                       if (event.which === 13) {
-                               this._active = false;
-                               
-                               return;
-                       }
-                       
-                       if (this._dropdownActive) {
-                               data.cancel = true;
-                               
-                               // ignore arrow up/down
-                               if (event.which === 38 || event.which === 40) {
-                                       return;
-                               }
-                       }
-                       
-                       var text = this._getTextLineInFrontOfCaret();
-                       if (text.length > 0 && text.length < 25) {
-                               var match = text.match(/@([^,]{3,})$/);
-                               if (match) {
-                                       // if mentioning is at text begin or there's a whitespace character
-                                       // before the '@', everything is fine
-                                       if (!match.index || text[match.index - 1].match(/\s/)) {
-                                               this._mentionStart = match[1];
-                                               
-                                               if (this._timer !== null) {
-                                                       window.clearTimeout(this._timer);
-                                                       this._timer = null;
-                                               }
-                                               
-                                               this._timer = window.setTimeout((function() {
-                                                       Ajax.api(this, {
-                                                               parameters: {
-                                                                       data: {
-                                                                               searchString: this._mentionStart
-                                                                       }
-                                                               }
-                                                       });
-                                                       
-                                                       this._timer = null;
-                                               }).bind(this), 500);
-                                       }
-                               }
-                               else {
-                                       this._hideDropdown();
-                               }
-                       }
-                       else {
-                               this._hideDropdown();
-                       }
-               },
-               
-               _getTextLineInFrontOfCaret: function() {
-                       var data = this._selectMention(false);
-                       if (data !== null) {
-                               return data.range.cloneContents().textContent.replace(/\u200B/g, '').replace(/\u00A0/g, ' ').trim();
-                       }
-                       
-                       return '';
-               },
-               
-               _getDropdownMenuPosition: function() {
-                       var data = this._selectMention();
-                       if (data === null) {
-                               return null;
-                       }
-                       
-                       this._redactor.selection.save();
-                       
-                       data.selection.removeAllRanges();
-                       data.selection.addRange(data.range);
-                       
-                       // get the offsets of the bounding box of current text selection
-                       var rect = data.selection.getRangeAt(0).getBoundingClientRect();
-                       var offsets = {
-                               top: Math.round(rect.bottom) + (window.scrollY || window.pageYOffset),
-                               left: Math.round(rect.left) + document.body.scrollLeft
-                       };
-                       
-                       if (this._lineHeight === null) {
-                               this._lineHeight = Math.round(rect.bottom - rect.top);
-                       }
-                       
-                       // restore caret position
-                       this._redactor.selection.restore();
-                       
-                       return offsets;
-               },
-               
-               _setUsername: function(event, item) {
-                       if (event) {
-                               event.preventDefault();
-                               item = event.currentTarget;
-                       }
-                       
-                       var data = this._selectMention();
-                       if (data === null) {
-                               this._hideDropdown();
-                               
-                               return;
-                       }
-                       
-                       // allow redactor to undo this
-                       this._redactor.buffer.set();
-                       
-                       data.selection.removeAllRanges();
-                       data.selection.addRange(data.range);
-                       
-                       var range = getSelection().getRangeAt(0);
-                       range.deleteContents();
-                       range.collapse(true);
-                       
-                       var text = document.createTextNode('@' + elData(item, 'username') + '\u00A0');
-                       range.insertNode(text);
-                       
-                       range = document.createRange();
-                       range.selectNode(text);
-                       range.collapse(false);
-                       
-                       data.selection.removeAllRanges();
-                       data.selection.addRange(range);
-                       
-                       this._hideDropdown();
-               },
-               
-               _selectMention: function (skipCheck) {
-                       var selection = window.getSelection();
-                       if (!selection.rangeCount || !selection.isCollapsed) {
-                               return null;
-                       }
-                       
-                       var container = selection.anchorNode;
-                       if (container.nodeType === Node.TEXT_NODE) {
-                               // work-around for Firefox after suggestions have been presented
-                               container = container.parentNode;
-                       }
-                       
-                       // check if there is an '@' within the current range
-                       if (container.textContent.indexOf('@') === -1) {
-                               return null;
-                       }
-                       
-                       // check if we're inside code or quote blocks
-                       var editor = this._redactor.core.editor()[0];
-                       while (container && container !== editor) {
-                               if (['PRE', 'WOLTLAB-QUOTE'].indexOf(container.nodeName) !== -1) {
-                                       return null;
-                               }
-                               
-                               container = container.parentNode;
-                       }
-                       
-                       var range = selection.getRangeAt(0);
-                       var endContainer = range.startContainer;
-                       var endOffset = range.startOffset;
-                       
-                       // find the appropriate end location
-                       while (endContainer.nodeType === Node.ELEMENT_NODE) {
-                               if (endOffset === 0 && endContainer.childNodes.length === 0) {
-                                       // invalid start location
-                                       return null;
-                               }
-                               
-                               // startOffset for elements will always be after a node index
-                               // or at the very start, which means if there is only text node
-                               // and the caret is after it, startOffset will equal `1`
-                               endContainer = endContainer.childNodes[(endOffset ? endOffset - 1 : 0)];
-                               if (endOffset > 0) {
-                                       if (endContainer.nodeType === Node.TEXT_NODE) {
-                                               endOffset = endContainer.textContent.length;
-                                       }
-                                       else {
-                                               endOffset = endContainer.childNodes.length;
-                                       }
-                               }
-                       }
-                       
-                       var startContainer = endContainer;
-                       var startOffset = -1;
-                       while (startContainer !== null) {
-                               if (startContainer.nodeType !== Node.TEXT_NODE) {
-                                       return null;
-                               }
-                               
-                               if (startContainer.textContent.indexOf('@') !== -1) {
-                                       startOffset = startContainer.textContent.lastIndexOf('@');
-                                       
-                                       break;
-                               }
-                               
-                               startContainer = startContainer.previousSibling;
-                       }
-                       
-                       if (startOffset === -1) {
-                               // there was a non-text node that was in our way
-                               return null;
-                       }
-                       
-                       try {
-                               // mark the entire text, starting from the '@' to the current cursor position
-                               range = document.createRange();
-                               range.setStart(startContainer, startOffset);
-                               range.setEnd(endContainer, endOffset);
-                       }
-                       catch (e) {
-                               window.console.debug(e);
-                               return null;
-                       }
-                       
-                       if (skipCheck === false) {
-                               // check if the `@` occurs at the very start of the container
-                               // or at least has a whitespace in front of it
-                               var text = '';
-                               if (startOffset) {
-                                       text = startContainer.textContent.substr(0, startOffset);
-                               }
-                               
-                               while (startContainer = startContainer.previousSibling) {
-                                       if (startContainer.nodeType === Node.TEXT_NODE) {
-                                               text = startContainer.textContent + text;
-                                       }
-                                       else {
-                                               break;
-                                       }
-                               }
-                               
-                               if (text.replace(/\u200B/g, '').match(/\S$/)) {
-                                       return null;
-                               }
-                       }
-                       else {
-                               // check if new range includes the mention text
-                               if (range.cloneContents().textContent.replace(/\u200B/g, '').replace(/\u00A0/g, '').trim().replace(/^@/, '') !== this._mentionStart) {
-                                       // string mismatch
-                                       return null;
-                               }
-                       }
-                       
-                       return {
-                               range: range,
-                               selection: selection
-                       };
-               },
-               
-               _updateDropdownPosition: function() {
-                       var offset = this._getDropdownMenuPosition();
-                       if (offset === null) {
-                               this._hideDropdown();
-                               
-                               return;
-                       }
-                       offset.top += 7; // add a little vertical gap
-                       
-                       this._dropdownMenu.style.setProperty('left', offset.left + 'px', '');
-                       this._dropdownMenu.style.setProperty('top', offset.top + 'px', '');
-                       
-                       this._selectItem(0);
-                       
-                       if (offset.top + this._dropdownMenu.offsetHeight + 10 > window.innerHeight + (window.scrollY || window.pageYOffset)) {
-                               this._dropdownMenu.style.setProperty('top', offset.top - this._dropdownMenu.offsetHeight - 2 * this._lineHeight + 7 + 'px', '');
-                       }
-               },
-               
-               _selectItem: function(step) {
-                       // find currently active item
-                       var item = elBySel('.active', this._dropdownMenu);
-                       if (item !== null) {
-                               item.classList.remove('active');
-                       }
-                       
-                       this._itemIndex += step;
-                       if (this._itemIndex < 0) {
-                               this._itemIndex = this._dropdownMenu.childElementCount - 1;
-                       }
-                       else if (this._itemIndex >= this._dropdownMenu.childElementCount) {
-                               this._itemIndex = 0;
-                       }
-                       
-                       this._dropdownMenu.children[this._itemIndex].classList.add('active');
-               },
-               
-               _hideDropdown: function() {
-                       if (this._dropdownMenu !== null) this._dropdownMenu.classList.remove('dropdownOpen');
-                       this._dropdownActive = false;
-                       this._itemIndex = 0;
-               },
-               
-               _ajaxSetup: function() {
-                       return {
-                               data: {
-                                       actionName: 'getSearchResultList',
-                                       className: 'wcf\\data\\user\\UserAction',
-                                       interfaceName: 'wcf\\data\\ISearchAction',
-                                       parameters: {
-                                               data: {
-                                                       includeUserGroups: true,
-                                                       scope: 'mention'
-                                               }
-                                       }
-                               },
-                               silent: true
-                       };
-               },
-               
-               _ajaxSuccess: function(data) {
-                       if (!Array.isArray(data.returnValues) || !data.returnValues.length) {
-                               this._hideDropdown();
-                               
-                               return;
-                       }
-                       
-                       if (this._dropdownMenu === null) {
-                               this._dropdownMenu = elCreate('ol');
-                               this._dropdownMenu.className = 'dropdownMenu';
-                               
-                               if (_dropdownContainer === null) {
-                                       _dropdownContainer = elCreate('div');
-                                       _dropdownContainer.className = 'dropdownMenuContainer';
-                                       document.body.appendChild(_dropdownContainer);
-                               }
-                               
-                               _dropdownContainer.appendChild(this._dropdownMenu);
-                       }
-                       
-                       this._dropdownMenu.innerHTML = '';
-                       
-                       var callbackClick = this._setUsername.bind(this), link, listItem, user;
-                       for (var i = 0, length = data.returnValues.length; i < length; i++) {
-                               user = data.returnValues[i];
-                               
-                               listItem = elCreate('li');
-                               link = elCreate('a');
-                               link.addEventListener('mousedown', callbackClick);
-                               link.className = 'box16';
-                               link.innerHTML = '<span>' + user.icon + '</span> <span>' + StringUtil.escapeHTML(user.label) + '</span>';
-                               elData(link, 'user-id', user.objectID);
-                               elData(link, 'username', user.label);
-                               
-                               listItem.appendChild(link);
-                               this._dropdownMenu.appendChild(listItem);
-                       }
-                       
-                       this._dropdownMenu.classList.add('dropdownOpen');
-                       this._dropdownActive = true;
-                       
-                       this._updateDropdownPosition();
-               }
-       };
-       
-       return UiRedactorMention;
-});
-
-/**
- * Converts `<woltlab-metacode>` into the bbcode representation.
- *
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Redactor/Page
- */
-define('WoltLabSuite/Core/Ui/Redactor/Page',['WoltLabSuite/Core/Ui/Page/Search'], function(UiPageSearch) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _click: function() {},
-                       _insert: function() {}
-               };
-               return Fake;
-       }
-       
-       function UiRedactorPage(editor, button) { this.init(editor, button); }
-       UiRedactorPage.prototype = {
-               init: function (editor, button) {
-                       this._editor = editor;
-                       
-                       button.addEventListener(WCF_CLICK_EVENT, this._click.bind(this));
-               },
-               
-               _click: function (event) {
-                       event.preventDefault();
-                       
-                       UiPageSearch.open(this._insert.bind(this));
-               },
-               
-               _insert: function (pageID) {
-                       this._editor.buffer.set();
-                       
-                       this._editor.insert.text("[wsp='" + pageID + "'][/wsp]");
-               }
-       };
-       
-       return UiRedactorPage;
-});
-
-/**
- * Manages quotes.
- *
- * @author      Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module      WoltLabSuite/Core/Ui/Redactor/Quote
- */
-define('WoltLabSuite/Core/Ui/Redactor/Quote',['Core', 'EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog', './Metacode', './PseudoHeader'], function (Core, EventHandler, EventKey, Language, StringUtil, DomUtil, UiDialog, UiRedactorMetacode, UiRedactorPseudoHeader) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _insertQuote: function() {},
-                       _click: function() {},
-                       _observeLoad: function() {},
-                       _edit: function() {},
-                       _save: function() {},
-                       _setTitle: function() {},
-                       _delete: function() {},
-                       _dialogSetup: function() {},
-                       _dialogSubmit: function() {}
-               };
-               return Fake;
-       }
-       
-       var _headerHeight = 0;
-       
-       /**
-        * @param       {Object}        editor  editor instance
-        * @param       {jQuery}        button  toolbar button
-        * @constructor
-        */
-       function UiRedactorQuote(editor, button) { this.init(editor, button); }
-       UiRedactorQuote.prototype = {
-               /**
-                * Initializes the quote management.
-                * 
-                * @param       {Object}        editor  editor instance
-                * @param       {jQuery}        button  toolbar button
-                */
-               init: function(editor, button) {
-                       this._quote = null;
-                       this._quotes = elByTag('woltlab-quote', editor.$editor[0]);
-                       this._editor = editor;
-                       this._elementId = this._editor.$element[0].id;
-                       
-                       EventHandler.add('com.woltlab.wcf.redactor2', 'observe_load_' + this._elementId, this._observeLoad.bind(this));
-                       
-                       this._editor.button.addCallback(button, this._click.bind(this));
-                       
-                       // static bind to ensure that removing works
-                       this._callbackEdit = this._edit.bind(this);
-                       
-                       // bind listeners on init
-                       this._observeLoad();
-                       
-                       // quote manager
-                       EventHandler.add('com.woltlab.wcf.redactor2', 'insertQuote_' + this._elementId, this._insertQuote.bind(this));
-               },
-               
-               /**
-                * Inserts a quote.
-                * 
-                * @param       {Object}        data            quote data
-                * @protected
-                */
-               _insertQuote: function (data) {
-                       if (this._editor.WoltLabSource.isActive()) {
-                               return;
-                       }
-                       
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'showEditor');
-                       
-                       var editor = this._editor.core.editor()[0];
-                       this._editor.selection.restore();
-                       
-                       this._editor.buffer.set();
-                       
-                       // caret must be within a `<p>`, if it is not: move it
-                       var block = this._editor.selection.block();
-                       if (block === false) {
-                               this._editor.focus.end();
-                               block = this._editor.selection.block();
-                       }
-                       
-                       while (block && block.parentNode !== editor) {
-                               block = block.parentNode;
-                       }
-                       
-                       var quote = elCreate('woltlab-quote');
-                       elData(quote, 'author', data.author);
-                       elData(quote, 'link', data.link);
-                       
-                       var content = data.content;
-                       if (data.isText) {
-                               content = StringUtil.escapeHTML(content);
-                               content = '<p>' + content + '</p>';
-                               content = content.replace(/\n\n/g, '</p><p>');
-                               content = content.replace(/\n/g, '<br>');
-                       }
-                       else {
-                               //noinspection JSUnresolvedFunction
-                               content = UiRedactorMetacode.convertFromHtml(this._editor.$element[0].id, content);
-                       }
-                       
-                       // bypass the editor as `insert.html()` doesn't like us
-                       quote.innerHTML = content;
-                       
-                       block.parentNode.insertBefore(quote, block.nextSibling);
-                       
-                       if (block.nodeName === 'P' && (block.innerHTML === '<br>' || block.innerHTML.replace(/\u200B/g, '') === '')) {
-                               block.parentNode.removeChild(block);
-                       }
-                       
-                       // avoid adjacent blocks that are not paragraphs
-                       var sibling = quote.previousElementSibling;
-                       if (sibling && sibling.nodeName !== 'P') {
-                               sibling = elCreate('p');
-                               sibling.textContent = '\u200B';
-                               quote.parentNode.insertBefore(sibling, quote);
-                       }
-                       
-                       this._editor.WoltLabCaret.paragraphAfterBlock(quote);
-                       
-                       this._editor.buffer.set();
-               },
-               
-               /**
-                * Toggles the quote block on button click.
-                * 
-                * @protected
-                */
-               _click: function() {
-                       this._editor.button.toggle({}, 'woltlab-quote', 'func', 'block.format');
-                       
-                       var quote = this._editor.selection.block();
-                       if (quote && quote.nodeName === 'WOLTLAB-QUOTE') {
-                               this._setTitle(quote);
-                               
-                               quote.addEventListener(WCF_CLICK_EVENT, this._callbackEdit);
-                               
-                               // work-around for Safari
-                               this._editor.caret.end(quote);
-                       }
-               },
-               
-               /**
-                * Binds event listeners and sets quote title on both editor
-                * initialization and when switching back from code view.
-                * 
-                * @protected
-                */
-               _observeLoad: function() {
-                       var quote;
-                       for (var i = 0, length = this._quotes.length; i < length; i++) {
-                               quote = this._quotes[i];
-                               
-                               quote.addEventListener('mousedown', this._callbackEdit);
-                               this._setTitle(quote);
-                       }
-               },
-               
-               /**
-                * Opens the dialog overlay to edit the quote's properties.
-                * 
-                * @param       {Event}         event           event object
-                * @protected
-                */
-               _edit: function(event) {
-                       var quote = event.currentTarget;
-                       
-                       if (_headerHeight === 0) {
-                               _headerHeight = UiRedactorPseudoHeader.getHeight(quote);
-                       }
-                       
-                       // check if the click hit the header
-                       var offset = DomUtil.offset(quote);
-                       if (event.pageY > offset.top && event.pageY < (offset.top + _headerHeight)) {
-                               event.preventDefault();
-                               
-                               this._editor.selection.save();
-                               this._quote = quote;
-                               
-                               UiDialog.open(this);
-                       }
-               },
-               
-               /**
-                * Saves the changes to the quote's properties.
-                * 
-                * @protected
-                */
-               _dialogSubmit: function() {
-                       var id = 'redactor-quote-' + this._elementId;
-                       var urlInput = elById(id + '-url');
-                       
-                       var url = urlInput.value.replace(/\u200B/g, '').trim();
-                       // simple test to check if it at least looks like it could be a valid url
-                       if (url.length && !/^https?:\/\/[^\/]+/.test(url)) {
-                               elInnerError(urlInput, Language.get('wcf.editor.quote.url.error.invalid'));
-                               return;
-                       }
-                       else {
-                               elInnerError(urlInput, false);
-                       }
-                       
-                       // set author
-                       elData(this._quote, 'author', elById(id + '-author').value);
-                       
-                       // set url
-                       elData(this._quote, 'link', url);
-                       
-                       this._setTitle(this._quote);
-                       this._editor.caret.after(this._quote);
-                       
-                       UiDialog.close(this);
-               },
-               
-               /**
-                * Sets or updates the quote's header title.
-                * 
-                * @param       {Element}       quote     quote element
-                * @protected
-                */
-               _setTitle: function(quote) {
-                       var title = Language.get('wcf.editor.quote.title', {
-                               author: elData(quote, 'author'),
-                               url: elData(quote, 'url')
-                       });
-                       
-                       if (elData(quote, 'title') !== title) {
-                               elData(quote, 'title', title);
-                       }
-               },
-               
-               _delete: function (event) {
-                       event.preventDefault();
-                       
-                       var caretEnd = this._quote.nextElementSibling || this._quote.previousElementSibling;
-                       if (caretEnd === null && this._quote.parentNode !== this._editor.core.editor()[0]) {
-                               caretEnd = this._quote.parentNode;
-                       }
-                       
-                       if (caretEnd === null) {
-                               this._editor.code.set('');
-                               this._editor.focus.end();
-                       }
-                       else {
-                               elRemove(this._quote);
-                               this._editor.caret.end(caretEnd);
-                       }
-                       
-                       UiDialog.close(this);
-               },
-               
-               _dialogSetup: function() {
-                       var id = 'redactor-quote-' + this._elementId,
-                           idAuthor = id + '-author',
-                           idButtonDelete = id + '-button-delete',
-                           idButtonSave = id + '-button-save',
-                           idUrl = id + '-url';
-                       
-                       return {
-                               id: id,
-                               options: {
-                                       onClose: (function () {
-                                               this._editor.selection.restore();
-                                               
-                                               UiDialog.destroy(this);
-                                       }).bind(this),
-                                       
-                                       onSetup: (function() {
-                                               elById(idButtonDelete).addEventListener(WCF_CLICK_EVENT, this._delete.bind(this));
-                                       }).bind(this),
-                                       
-                                       onShow: (function() {
-                                               elById(idAuthor).value = elData(this._quote, 'author');
-                                               elById(idUrl).value = elData(this._quote, 'link');
-                                       }).bind(this),
-                                       
-                                       title: Language.get('wcf.editor.quote.edit')
-                               },
-                               source: '<div class="section">'
-                                       + '<dl>'
-                                               + '<dt><label for="' + idAuthor + '">' + Language.get('wcf.editor.quote.author') + '</label></dt>'
-                                               + '<dd>'
-                                                       + '<input type="text" id="' + idAuthor + '" class="long" data-dialog-submit-on-enter="true">'
-                                               + '</dd>'
-                                       + '</dl>'
-                                       + '<dl>'
-                                               + '<dt><label for="' + idUrl + '">' + Language.get('wcf.editor.quote.url') + '</label></dt>'
-                                               + '<dd>'
-                                                       + '<input type="text" id="' + idUrl + '" class="long" data-dialog-submit-on-enter="true">'
-                                                       + '<small>' + Language.get('wcf.editor.quote.url.description') + '</small>'
-                                               + '</dd>'
-                                       + '</dl>'
-                               + '</div>'
-                               + '<div class="formSubmit">'
-                                       + '<button id="' + idButtonSave + '" class="buttonPrimary" data-type="submit">' + Language.get('wcf.global.button.save') + '</button>'
-                                       + '<button id="' + idButtonDelete + '">' + Language.get('wcf.global.button.delete') + '</button>'
-                               + '</div>'
-                       };
-               }
-       };
-       
-       return UiRedactorQuote;
-});
-/**
- * Manages spoilers.
- *
- * @author      Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module      WoltLabSuite/Core/Ui/Redactor/Spoiler
- */
-define('WoltLabSuite/Core/Ui/Redactor/Spoiler',['EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog', './PseudoHeader'], function (EventHandler, EventKey, Language, StringUtil, DomUtil, UiDialog, UiRedactorPseudoHeader) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _bbcodeSpoiler: function() {},
-                       _observeLoad: function() {},
-                       _edit: function() {},
-                       _setTitle: function() {},
-                       _delete: function() {},
-                       _dialogSetup: function() {},
-                       _dialogSubmit: function() {}
-               };
-               return Fake;
-       }
-       
-       var _headerHeight = 0;
-       
-       /**
-        * @param       {Object}        editor  editor instance
-        * @constructor
-        */
-       function UiRedactorSpoiler(editor) { this.init(editor); }
-       UiRedactorSpoiler.prototype = {
-               /**
-                * Initializes the spoiler management.
-                * 
-                * @param       {Object}        editor  editor instance
-                */
-               init: function(editor) {
-                       this._editor = editor;
-                       this._elementId = this._editor.$element[0].id;
-                       this._spoiler = null;
-                       
-                       EventHandler.add('com.woltlab.wcf.redactor2', 'bbcode_spoiler_' + this._elementId, this._bbcodeSpoiler.bind(this));
-                       EventHandler.add('com.woltlab.wcf.redactor2', 'observe_load_' + this._elementId, this._observeLoad.bind(this));
-                       
-                       // static bind to ensure that removing works
-                       this._callbackEdit = this._edit.bind(this);
-                       
-                       // bind listeners on init
-                       this._observeLoad();
-               },
-               
-               /**
-                * Intercepts the insertion of `[spoiler]` tags and uses
-                * the custom `<woltlab-spoiler>` element instead.
-                * 
-                * @param       {Object}        data    event data
-                * @protected
-                */
-               _bbcodeSpoiler: function(data) {
-                       data.cancel = true;
-                       
-                       this._editor.button.toggle({}, 'woltlab-spoiler', 'func', 'block.format');
-                       
-                       var spoiler = this._editor.selection.block();
-                       if (spoiler && spoiler.nodeName === 'WOLTLAB-SPOILER') {
-                               this._setTitle(spoiler);
-                               
-                               spoiler.addEventListener(WCF_CLICK_EVENT, this._callbackEdit);
-                               
-                               // work-around for Safari
-                               this._editor.caret.end(spoiler);
-                       }
-               },
-               
-               /**
-                * Binds event listeners and sets quote title on both editor
-                * initialization and when switching back from code view.
-                * 
-                * @protected
-                */
-               _observeLoad: function() {
-                       elBySelAll('woltlab-spoiler', this._editor.$editor[0], (function(spoiler) {
-                               spoiler.addEventListener('mousedown', this._callbackEdit);
-                               this._setTitle(spoiler);
-                       }).bind(this));
-               },
-               
-               /**
-                * Opens the dialog overlay to edit the spoiler's properties.
-                * 
-                * @param       {Event}         event           event object
-                * @protected
-                */
-               _edit: function(event) {
-                       var spoiler = event.currentTarget;
-                       
-                       if (_headerHeight === 0) {
-                               _headerHeight = UiRedactorPseudoHeader.getHeight(spoiler);
-                       }
-                       
-                       // check if the click hit the header
-                       var offset = DomUtil.offset(spoiler);
-                       if (event.pageY > offset.top && event.pageY < (offset.top + _headerHeight)) {
-                               event.preventDefault();
-                               
-                               this._editor.selection.save();
-                               this._spoiler = spoiler;
-                               
-                               UiDialog.open(this);
-                       }
-               },
-               
-               /**
-                * Saves the changes to the spoiler's properties.
-                * 
-                * @protected
-                */
-               _dialogSubmit: function() {
-                       elData(this._spoiler, 'label', elById('redactor-spoiler-' + this._elementId + '-label').value);
-                       
-                       this._setTitle(this._spoiler);
-                       this._editor.caret.after(this._spoiler);
-                       
-                       UiDialog.close(this);
-               },
-               
-               /**
-                * Sets or updates the spoiler's header title.
-                * 
-                * @param       {Element}       spoiler     spoiler element
-                * @protected
-                */
-               _setTitle: function(spoiler) {
-                       var title = Language.get('wcf.editor.spoiler.title', { label: elData(spoiler, 'label') });
-                       
-                       if (elData(spoiler, 'title') !== title) {
-                               elData(spoiler, 'title', title);
-                       }
-               },
-               
-               _delete: function (event) {
-                       event.preventDefault();
-                       
-                       var caretEnd = this._spoiler.nextElementSibling || this._spoiler.previousElementSibling;
-                       if (caretEnd === null && this._spoiler.parentNode !== this._editor.core.editor()[0]) {
-                               caretEnd = this._spoiler.parentNode;
-                       }
-                       
-                       if (caretEnd === null) {
-                               this._editor.code.set('');
-                               this._editor.focus.end();
-                       }
-                       else {
-                               elRemove(this._spoiler);
-                               this._editor.caret.end(caretEnd);
-                       }
-                       
-                       UiDialog.close(this);
-               },
-               
-               _dialogSetup: function() {
-                       var id = 'redactor-spoiler-' + this._elementId,
-                           idButtonDelete = id + '-button-delete',
-                           idButtonSave = id + '-button-save',
-                           idLabel = id + '-label';
-                       
-                       return {
-                               id: id,
-                               options: {
-                                       onClose: (function () {
-                                               this._editor.selection.restore();
-                                               
-                                               UiDialog.destroy(this);
-                                       }).bind(this),
-                                       
-                                       onSetup: (function() {
-                                               elById(idButtonDelete).addEventListener(WCF_CLICK_EVENT, this._delete.bind(this));
-                                       }).bind(this),
-                                       
-                                       onShow: (function() {
-                                               elById(idLabel).value = elData(this._spoiler, 'label');
-                                       }).bind(this),
-                                       
-                                       title: Language.get('wcf.editor.spoiler.edit')
-                               },
-                               source: '<div class="section">'
-                                       + '<dl>'
-                                               + '<dt><label for="' + idLabel + '">' + Language.get('wcf.editor.spoiler.label') + '</label></dt>'
-                                               + '<dd>'
-                                                       + '<input type="text" id="' + idLabel + '" class="long" data-dialog-submit-on-enter="true">'
-                                                       + '<small>' + Language.get('wcf.editor.spoiler.label.description') + '</small>'
-                                               + '</dd>'
-                                       + '</dl>'
-                               + '</div>'
-                               + '<div class="formSubmit">'
-                                       + '<button id="' + idButtonSave + '" class="buttonPrimary" data-type="submit">' + Language.get('wcf.global.button.save') + '</button>'
-                                       + '<button id="' + idButtonDelete + '">' + Language.get('wcf.global.button.delete') + '</button>'
-                               + '</div>'
-                       };
-               }
-       };
-       
-       return UiRedactorSpoiler;
-});
-define('WoltLabSuite/Core/Ui/Redactor/Table',['Language', 'Ui/Dialog'], function(Language, UiDialog) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       showDialog: function() {},
-                       _submit: function() {},
-                       _dialogSetup: function() {}
-               };
-               return Fake;
-       }
-       
-       var _callback = null;
-       
-       return {
-               showDialog: function(options) {
-                       UiDialog.open(this);
-                       
-                       _callback = options.submitCallback;
-               },
-               
-               _dialogSubmit: function() {
-                       // check if rows and cols are within the boundaries
-                       var isValid = true;
-                       ['rows', 'cols'].forEach(function(type) {
-                               var input = elById('redactor-table-' + type);
-                               if (input.value < 1 || input.value > 100) {
-                                       isValid = false;
-                               }
-                       });
-                       
-                       if (!isValid) return;
-                       
-                       _callback();
-                       
-                       UiDialog.close(this);
-               },
-               
-               _dialogSetup: function() {
-                       return {
-                               id: 'redactorDialogTable',
-                               options: {
-                                       onShow: function () {
-                                               elById('redactor-table-rows').value = 2;
-                                               elById('redactor-table-cols').value = 3;
-                                       },
-                                       title: Language.get('wcf.editor.table.insertTable')
-                               },
-                               source: '<dl>'
-                                               + '<dt><label for="redactor-table-rows">' + Language.get('wcf.editor.table.rows') + '</label></dt>'
-                                               + '<dd><input type="number" id="redactor-table-rows" class="small" min="1" max="100" value="2" data-dialog-submit-on-enter="true"></dd>'
-                                       + '</dl>'
-                                       + '<dl>'
-                                               + '<dt><label for="redactor-table-cols">' + Language.get('wcf.editor.table.cols') + '</label></dt>'
-                                               + '<dd><input type="number" id="redactor-table-cols" class="small" min="1" max="100" value="3" data-dialog-submit-on-enter="true"></dd>'
-                                       + '</dl>'
-                                       + '<div class="formSubmit">'
-                                               + '<button id="redactor-modal-button-action" class="buttonPrimary" data-type="submit">' + Language.get('wcf.global.button.insert') + '</button>'
-                                       + '</div>'
-                       };
-               }
-       };
-});
-
-define('WoltLabSuite/Core/Ui/Search/Page',['Core', 'Dom/Traverse', 'Dom/Util', 'Ui/Screen', 'Ui/SimpleDropdown', './Input'], function(Core, DomTraverse, DomUtil, UiScreen, UiSimpleDropdown, UiSearchInput) {
-       "use strict";
-       
-       return {
-               init: function (objectType) {
-                       var searchInput = elById('pageHeaderSearchInput');
-                       
-                       new UiSearchInput(searchInput, {
-                               ajax: {
-                                       className: 'wcf\\data\\search\\keyword\\SearchKeywordAction'
-                               },
-                               callbackDropdownInit: function(dropdownMenu) {
-                                       dropdownMenu.classList.add('dropdownMenuPageSearch');
-                                       
-                                       if (UiScreen.is('screen-lg')) {
-                                               elData(dropdownMenu, 'dropdown-alignment-horizontal', 'right');
-                                               
-                                               var minWidth = searchInput.clientWidth;
-                                               dropdownMenu.style.setProperty('min-width', minWidth + 'px', '');
-                                               
-                                               // calculate offset to ignore the width caused by the submit button
-                                               var parent = searchInput.parentNode;
-                                               var offsetRight = (DomUtil.offset(parent).left + parent.clientWidth) - (DomUtil.offset(searchInput).left + minWidth);
-                                               var offsetTop = DomUtil.styleAsInt(window.getComputedStyle(parent), 'padding-bottom');
-                                               dropdownMenu.style.setProperty('transform', 'translateX(-' + Math.ceil(offsetRight) + 'px) translateY(-' + offsetTop + 'px)', '');
-                                       }
-                               },
-                               callbackSelect: function() {
-                                       setTimeout(function() {
-                                               DomTraverse.parentByTag(searchInput, 'FORM').submit();
-                                       }, 1);
-                                       
-                                       return true;
-                               }
-                       });
-                       
-                       var dropdownMenu = UiSimpleDropdown.getDropdownMenu(DomUtil.identify(elBySel('.pageHeaderSearchType')));
-                       var callback = this._click.bind(this);
-                       elBySelAll('a[data-object-type]', dropdownMenu, function(link) {
-                               link.addEventListener(WCF_CLICK_EVENT, callback);
-                       });
-                       
-                       // trigger click on init
-                       var link = elBySel('a[data-object-type="' + objectType + '"]', dropdownMenu);
-                       Core.triggerEvent(link, WCF_CLICK_EVENT);
-               },
-               
-               _click: function(event) {
-                       event.preventDefault();
-                       
-                       var pageHeader = elById('pageHeader');
-                       pageHeader.classList.add('searchBarForceOpen');
-                       window.setTimeout(function() {
-                               pageHeader.classList.remove('searchBarForceOpen');
-                       }, 10);
-                       
-                       var objectType = elData(event.currentTarget, 'object-type');
-                       
-                       var container = elById('pageHeaderSearchParameters');
-                       container.innerHTML = '';
-                       
-                       var extendedLink = elData(event.currentTarget, 'extended-link');
-                       if (extendedLink) {
-                               elBySel('.pageHeaderSearchExtendedLink').href = extendedLink;
-                       }
-                       
-                       var parameters = elData(event.currentTarget, 'parameters');
-                       if (parameters) {
-                               parameters = JSON.parse(parameters);
-                       }
-                       else {
-                               parameters = {};
-                       }
-                       
-                       if (objectType) parameters['types[]'] = objectType;
-                       
-                       for (var key in parameters) {
-                               if (parameters.hasOwnProperty(key)) {
-                                       var input = elCreate('input');
-                                       input.type = 'hidden';
-                                       input.name = key;
-                                       input.value = parameters[key];
-                                       container.appendChild(input);
-                               }
-                       }
-                       
-                       // update label
-                       var button = elBySel('.pageHeaderSearchType > .button > .pageHeaderSearchTypeLabel', elById('pageHeaderSearchInputContainer'));
-                       button.textContent = event.currentTarget.textContent;
-               }
-       };
-});
-
-/**
- * Inserts smilies into a WYSIWYG editor instance, with WAI-ARIA keyboard support.
- * 
- * @author      Alexander Ebert
- * @copyright   2001-2019 WoltLab GmbH
- * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module      WoltLabSuite/Core/Ui/Smiley/Insert
- */
-define('WoltLabSuite/Core/Ui/Smiley/Insert',['EventHandler', 'EventKey'], function (EventHandler, EventKey) {
-       'use strict';
-       
-       function UiSmileyInsert(editorId) { this.init(editorId); }
-       
-       UiSmileyInsert.prototype = {
-               _editorId: '',
-               
-               /**
-                * @param {string} editorId
-                */
-               init: function (editorId) {
-                       this._editorId = editorId;
-                       
-                       var container = elById('smilies-' + this._editorId);
-                       if (!container) {
-                               // form builder
-                               container = elById(this._editorId + 'SmiliesTabContainer');
-                               if (!container) {
-                                       throw new Error('Unable to find the message tab menu container containing the smilies.');
-                               }
-                       }
-                       
-                       container.addEventListener('keydown', this._keydown.bind(this));
-                       container.addEventListener('mousedown', this._mousedown.bind(this));
-               },
-               
-               /**
-                * @param {KeyboardEvent} event
-                * @protected
-                */
-               _keydown: function(event) {
-                       var activeButton = document.activeElement;
-                       if (!activeButton.classList.contains('jsSmiley')) {
-                               return;
-                       }
-                       
-                       if (EventKey.ArrowLeft(event) || EventKey.ArrowRight(event) || EventKey.Home(event) || EventKey.End(event)) {
-                               event.preventDefault();
-                               
-                               var smilies = Array.prototype.slice.call(elBySelAll('.jsSmiley', event.currentTarget));
-                               if (EventKey.ArrowLeft(event)) {
-                                       smilies.reverse();
-                               }
-                               
-                               var index = smilies.indexOf(activeButton);
-                               if (EventKey.Home(event)) {
-                                       index = 0;
-                               }
-                               else if (EventKey.End(event)) {
-                                       index = smilies.length - 1;
-                               }
-                               else {
-                                       index = index + 1;
-                                       if (index === smilies.length) {
-                                               index = 0;
-                                       }
-                               }
-                               
-                               smilies[index].focus();
-                       }
-                       else if (EventKey.Enter(event) || EventKey.Space(event)) {
-                               event.preventDefault();
-                               
-                               this._insert(elBySel('img', activeButton));
-                       }
-               },
-               
-               /**
-                * @param {MouseEvent} event
-                * @protected
-                */
-               _mousedown: function (event) {
-                       event.preventDefault();
-                       
-                       // Clicks may occur on a few different elements, but we are only looking for the image.
-                       var listItem = event.target.closest('li');
-                       var img = elBySel('img', listItem);
-                       if (img) this._insert(img);
-               },
-               
-               /**
-                * @param {Element} img
-                * @protected
-                */
-               _insert: function(img) {
-                       EventHandler.fire('com.woltlab.wcf.redactor2', 'insertSmiley_' + this._editorId, {
-                               img: img
-                       });
-               }
-       };
-       return UiSmileyInsert;
-});
-
-/**
- * Provides a selection dialog for FontAwesome icons with filter capabilities.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Style/FontAwesome
- */
-define('WoltLabSuite/Core/Ui/Style/FontAwesome',['Language', 'Ui/Dialog', 'WoltLabSuite/Core/Ui/ItemList/Filter'], function (Language, UiDialog, UiItemListFilter) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       setup: function() {},
-                       open: function() {},
-                       _click: function() {},
-                       _dialogSetup: function() {}
-               };
-               return Fake;
-       }
-       
-       var _callback, _iconList, _itemListFilter;
-       var _icons = [];
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Style/FontAwesome
-        */
-       return {
-               /**
-                * Sets the list of available icons, must be invoked prior to any call
-                * to the `open()` method.
-                * 
-                * @param       {string[]}      icons   list of icon names excluding the `fa-` prefix
-                */
-               setup: function (icons) {
-                       _icons = icons;
-               },
-               
-               /**
-                * Shows the FontAwesome selection dialog, supplied callback will be
-                * invoked with the selection icon's name as the only argument.
-                * 
-                * @param       {Function<string>}      callback        callback on icon selection, receives icon name only
-                */
-               open: function(callback) {
-                       if (_icons.length === 0) {
-                               throw new Error("Missing icon data, please include the template before calling this method using `{include file='fontAwesomeJavaScript'}`.");
-                       }
-                       
-                       _callback = callback;
-                       
-                       UiDialog.open(this);
-               },
-               
-               /**
-                * Selects an icon, notifies the callback and closes the dialog.
-                * 
-                * @param       {Event}         event           event object
-                * @protected
-                */
-               _click: function(event) {
-                       event.preventDefault();
-                       
-                       var item = event.target.closest('li');
-                       var icon = elBySel('small', item).textContent.trim();
-                       
-                       UiDialog.close(this);
-                       
-                       _callback(icon);
-               },
-               
-               _dialogSetup: function() {
-                       return {
-                               id: 'fontAwesomeSelection',
-                               options: {
-                                       onSetup: (function() {
-                                               _iconList = elById('fontAwesomeIcons');
-                                               
-                                               // build icons
-                                               var icon, html = '';
-                                               for (var i = 0, length = _icons.length; i < length; i++) {
-                                                       icon = _icons[i];
-                                                       
-                                                       html += '<li><span class="icon icon48 fa-' + icon + '"></span><small>' + icon + '</small></li>';
-                                               }
-                                               
-                                               _iconList.innerHTML = html;
-                                               _iconList.addEventListener(WCF_CLICK_EVENT, this._click.bind(this));
-                                               
-                                               _itemListFilter = new UiItemListFilter('fontAwesomeIcons', {
-                                                       callbackPrepareItem: function (item) {
-                                                               var small = elBySel('small', item);
-                                                               var text = small.textContent.trim();
-                                                               
-                                                               return {
-                                                                       item: item,
-                                                                       span: small,
-                                                                       text: text
-                                                               };
-                                                       },
-                                                       enableVisibilityFilter: false
-                                               });
-                                       }).bind(this),
-                                       onShow: function () {
-                                               _itemListFilter.reset();
-                                       },
-                                       title: Language.get('wcf.global.fontAwesome.selectIcon')
-                               },
-                               source: '<ul class="fontAwesomeIcons" id="fontAwesomeIcons"></ul>'
-                       };
-               }
-       }
-});
-/**
- * Provides a simple toggle to show or hide certain elements when the
- * target element is checked.
- * 
- * Be aware that the list of elements to show or hide accepts selectors
- * which will be passed to `elBySel()`, causing only the first matched
- * element to be used. If you require a whole list of elements identified
- * by a single selector to be handled, please provide the actual list of
- * elements instead.
- * 
- * Usage:
- * 
- * new UiToggleInput('input[name="foo"][value="bar"]', {
- *      show: ['#showThisContainer', '.makeThisVisibleToo'],
- *      hide: ['.notRelevantStuff', elById('fooBar')]
- * });
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Toggle/Input
- */
-define('WoltLabSuite/Core/Ui/Toggle/Input',['Core'], function(Core) {
-       "use strict";
-       
-       /**
-        * @param       {string}        elementSelector         element selector used with `elBySel()`
-        * @param       {Object}        options                 toggle options
-        * @constructor
-        */
-       function UiToggleInput(elementSelector, options) { this.init(elementSelector, options); }
-       UiToggleInput.prototype = {
-               /**
-                * Initializes a new input toggle.
-                * 
-                * @param       {string}        elementSelector         element selector used with `elBySel()`
-                * @param       {Object}        options                 toggle options
-                */
-               init: function(elementSelector, options) {
-                       this._element = elBySel(elementSelector);
-                       if (this._element === null) {
-                               throw new Error("Unable to find element by selector '" + elementSelector + "'.");
-                       }
-                       
-                       var type = (this._element.nodeName === 'INPUT') ? elAttr(this._element, 'type') : '';
-                       if (type !== 'checkbox' && type !== 'radio') {
-                               throw new Error("Illegal element, expected input[type='checkbox'] or input[type='radio'].");
-                       }
-                       
-                       this._options = Core.extend({
-                               hide: [],
-                               show: []
-                       }, options);
-                       
-                       ['hide', 'show'].forEach((function(type) {
-                               var element, i, length;
-                               for (i = 0, length = this._options[type].length; i < length; i++) {
-                                       element = this._options[type][i];
-                                       
-                                       if (typeof element !== 'string' && !(element instanceof Element)) {
-                                               throw new TypeError("The array '" + type + "' may only contain string selectors or DOM elements.");
-                                       }
-                               }
-                       }).bind(this));
-                       
-                       this._element.addEventListener('change', this._change.bind(this));
-                       
-                       this._handleElements(this._options.show, this._element.checked);
-                       this._handleElements(this._options.hide, !this._element.checked);
-               },
-               
-               /**
-                * Triggered when element is checked / unchecked.
-                * 
-                * @param       {Event}         event   event object
-                * @protected
-                */
-               _change: function(event) {
-                       var showElements = event.currentTarget.checked;
-                       
-                       this._handleElements(this._options.show, showElements);
-                       this._handleElements(this._options.hide, !showElements);
-               },
-               
-               /**
-                * Loops through the target elements and shows / hides them.
-                * 
-                * @param       {Array}         elements        list of elements or selectors
-                * @param       {boolean}       showElement     true if elements should be shown
-                * @protected
-                */
-               _handleElements: function(elements, showElement) {
-                       var element, tmp;
-                       for (var i = 0, length = elements.length; i < length; i++) {
-                               element = elements[i];
-                               if (typeof element === 'string') {
-                                       tmp = elBySel(element);
-                                       if (tmp === null) {
-                                               throw new Error("Unable to find element by selector '" + element + "'.");
-                                       }
-                                       
-                                       elements[i] = element = tmp;
-                               }
-                               
-                               window[(showElement ? 'elShow' : 'elHide')](element);
-                       }
-               }
-       };
-       
-       return UiToggleInput;
-});
-
-/**
- * Simple notification overlay.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/User/Editor
- */
-define('WoltLabSuite/Core/Ui/User/Editor',['Ajax', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog', 'Ui/Notification'], function(Ajax, Language, StringUtil, DomUtil, UiDialog, UiNotification) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       _click: function() {},
-                       _submit: function() {},
-                       _ajaxSuccess: function() {},
-                       _ajaxSetup: function() {},
-                       _dialogSetup: function() {}
-               };
-               return Fake;
-       }
-       
-       var _actionName = '';
-       var _userHeader = null;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/User/Editor
-        */
-       return {
-               /**
-                * Initializes the user editor.
-                */
-               init: function() {
-                       _userHeader = elBySel('.userProfileUser');
-                       
-                       // init buttons
-                       ['ban', 'disableAvatar', 'disableCoverPhoto', 'disableSignature', 'enable'].forEach((function(action) {
-                               var button = elBySel('.userProfileButtonMenu .jsButtonUser' + StringUtil.ucfirst(action));
-                               
-                               // button is missing if users lacks the permission
-                               if (button) {
-                                       elData(button, 'action', action);
-                                       button.addEventListener(WCF_CLICK_EVENT, this._click.bind(this));
-                               }
-                       }).bind(this));
-               },
-               
-               /**
-                * Handles clicks on action buttons.
-                * 
-                * @param       {Event}         event           event object
-                * @protected
-                */
-               _click: function(event) {
-                       event.preventDefault();
-                       
-                       //noinspection JSCheckFunctionSignatures
-                       var action = elData(event.currentTarget, 'action');
-                       var actionName = '';
-                       switch (action) {
-                               case 'ban':
-                                       if (elDataBool(_userHeader, 'banned')) {
-                                               actionName = 'unban';
-                                       }
-                                       break;
-                               
-                               case 'disableAvatar':
-                                       if (elDataBool(_userHeader, 'disable-avatar')) {
-                                               actionName = 'enableAvatar';
-                                       }
-                                       break;
-                                       
-                               case 'disableCoverPhoto':
-                                       if (elDataBool(_userHeader, 'disable-cover-photo')) {
-                                               actionName = 'enableCoverPhoto';
-                                       }
-                                       break;
-                               
-                               case 'disableSignature':
-                                       if (elDataBool(_userHeader, 'disable-signature')) {
-                                               actionName = 'enableSignature';
-                                       }
-                                       break;
-                               
-                               case 'enable':
-                                       actionName = (elDataBool(_userHeader, 'is-disabled')) ? 'enable' : 'disable';
-                                       break;
-                       }
-                       
-                       if (actionName === '') {
-                               _actionName = action;
-                               
-                               UiDialog.open(this);
-                       }
-                       else {
-                               Ajax.api(this, {
-                                       actionName: actionName
-                               });
-                       }
-               },
-               
-               /**
-                * Handles form submit and input validation.
-                * 
-                * @param       {Event}         event           event object
-                * @protected
-                */
-               _submit: function(event) {
-                       event.preventDefault();
-                       
-                       var label = elById('wcfUiUserEditorExpiresLabel');
-                       
-                       var expires = '';
-                       var errorMessage = '';
-                       if (!elById('wcfUiUserEditorNeverExpires').checked) {
-                               expires = elById('wcfUiUserEditorExpiresDatePicker').value;
-                               if (expires === '') {
-                                       errorMessage = Language.get('wcf.global.form.error.empty');
-                               }
-                       }
-                       
-                       elInnerError(label, errorMessage);
-                       
-                       var parameters = {};
-                       parameters[_actionName + 'Expires'] = expires;
-                       parameters[_actionName + 'Reason'] = elById('wcfUiUserEditorReason').value.trim();
-                       
-                       Ajax.api(this, {
-                               actionName: _actionName,
-                               parameters: parameters
-                       });
-               },
-               
-               _ajaxSuccess: function(data) {
-                       switch (data.actionName) {
-                               case 'ban':
-                               case 'unban':
-                                       elData(_userHeader, 'banned', (data.actionName === 'ban'));
-                                       elBySel('.userProfileButtonMenu .jsButtonUserBan').textContent = Language.get('wcf.user.' + (data.actionName === 'ban' ? 'unban' : 'ban'));
-                                       
-                                       var contentTitle = elBySel('.contentTitle', _userHeader);
-                                       var banIcon = elBySel('.jsUserBanned', contentTitle);
-                                       if (data.actionName === 'ban') {
-                                               banIcon = elCreate('span');
-                                               banIcon.className = 'icon icon24 fa-lock jsUserBanned jsTooltip';
-                                               banIcon.title = data.returnValues;
-                                               contentTitle.appendChild(banIcon);
-                                       }
-                                       else if (banIcon) {
-                                               elRemove(banIcon);
-                                       }
-                                       
-                                       break;
-                               
-                               case 'disableAvatar':
-                               case 'enableAvatar':
-                                       elData(_userHeader, 'disable-avatar', (data.actionName === 'disableAvatar'));
-                                       elBySel('.userProfileButtonMenu .jsButtonUserDisableAvatar').textContent = Language.get('wcf.user.' + (data.actionName === 'disableAvatar' ? 'enable' : 'disable') + 'Avatar');
-                                       
-                                       break;
-                                       
-                               case 'disableCoverPhoto':
-                               case 'enableCoverPhoto':
-                                       elData(_userHeader, 'disable-cover-photo', (data.actionName === 'disableCoverPhoto'));
-                                       elBySel('.userProfileButtonMenu .jsButtonUserDisableCoverPhoto').textContent = Language.get('wcf.user.' + (data.actionName === 'disableCoverPhoto' ? 'enable' : 'disable') + 'CoverPhoto');
-                                       
-                                       break;
-                                       
-                               case 'disableSignature':
-                               case 'enableSignature':
-                                       elData(_userHeader, 'disable-signature', (data.actionName === 'disableSignature'));
-                                       elBySel('.userProfileButtonMenu .jsButtonUserDisableSignature').textContent = Language.get('wcf.user.' + (data.actionName === 'disableSignature' ? 'enable' : 'disable') + 'Signature');
-                                       
-                                       break;
-                               
-                               case 'enable':
-                               case 'disable':
-                                       elData(_userHeader, 'is-disabled', (data.actionName === 'disable'));
-                                       elBySel('.userProfileButtonMenu .jsButtonUserEnable').textContent = Language.get('wcf.acp.user.' + (data.actionName === 'enable' ? 'disable' : 'enable'));
-                                       
-                                       break;
-                       }
-                       
-                       if (data.actionName === 'ban' || data.actionName === 'disableAvatar' || data.actionName === 'disableCoverPhoto' || data.actionName === 'disableSignature') {
-                               UiDialog.close(this);
-                       }
-                       
-                       UiNotification.show();
-               },
-               
-               _ajaxSetup: function () {
-                       return {
-                               data: {
-                                       className: 'wcf\\data\\user\\UserAction',
-                                       objectIDs: [ elData(_userHeader, 'object-id') ]
-                               }
-                       };
-               },
-               
-               _dialogSetup: function() {
-                       return {
-                               id: 'wcfUiUserEditor',
-                               options: {
-                                       onSetup: (function (content) {
-                                               elById('wcfUiUserEditorNeverExpires').addEventListener('change', function () {
-                                                       window[(this.checked) ? 'elHide' : 'elShow'](elById('wcfUiUserEditorExpiresSettings'));
-                                               });
-                                               
-                                               elBySel('button.buttonPrimary', content).addEventListener(WCF_CLICK_EVENT, this._submit.bind(this));
-                                       }).bind(this),
-                                       onShow: function(content) {
-                                               UiDialog.setTitle('wcfUiUserEditor', Language.get('wcf.user.' + _actionName + '.confirmMessage'));
-                                               
-                                               var label = elById('wcfUiUserEditorReason').nextElementSibling;
-                                               var phrase = 'wcf.user.' + _actionName + '.reason.description';
-                                               label.textContent = Language.get(phrase);
-                                               window[(label.textContent === phrase) ? 'elHide' : 'elShow'](label);
-                                               
-                                               label = elById('wcfUiUserEditorNeverExpires').nextElementSibling;
-                                               label.textContent = Language.get('wcf.user.' + _actionName + '.neverExpires');
-                                               
-                                               label = elBySel('label[for="wcfUiUserEditorExpires"]', content);
-                                               label.textContent = Language.get('wcf.user.' + _actionName + '.expires');
-                                               
-                                               label = elById('wcfUiUserEditorExpiresLabel');
-                                               label.textContent = Language.get('wcf.user.' + _actionName + '.expires.description');
-                                       }
-                               },
-                               source: '<div class="section">'
-                                               + '<dl>'
-                                                       + '<dt><label for="wcfUiUserEditorReason">' + Language.get('wcf.global.reason') + '</label></dt>'
-                                                       + '<dd><textarea id="wcfUiUserEditorReason" cols="40" rows="3"></textarea><small></small></dd>'
-                                               + '</dl>'
-                                               + '<dl>'
-                                                       + '<dt></dt>'
-                                                       + '<dd><label><input type="checkbox" id="wcfUiUserEditorNeverExpires" checked> <span></span></label></dd>'
-                                               + '</dl>'
-                                               + '<dl id="wcfUiUserEditorExpiresSettings" style="display: none">'
-                                                       + '<dt><label for="wcfUiUserEditorExpires"></label></dt>'
-                                                       + '<dd>'
-                                                               + '<input type="date" name="wcfUiUserEditorExpires" id="wcfUiUserEditorExpires" class="medium" min="' + new Date(TIME_NOW * 1000).toISOString() + '" data-ignore-timezone="true">'
-                                                               + '<small id="wcfUiUserEditorExpiresLabel"></small>'
-                                                       + '</dd>'
-                                               +'</dl>'
-                                       + '</div>'
-                                       + '<div class="formSubmit"><button class="buttonPrimary">' + Language.get('wcf.global.button.submit') + '</button></div>'
-                       };
-               }
-       };
-});
-
-/**
- * Shows and hides an element that depends on certain selected pages when setting up conditions.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Controller/Condition/Page/Dependence
- */
-define('WoltLabSuite/Core/Controller/Condition/Page/Dependence',['Dom/ChangeListener', 'Dom/Traverse', 'EventHandler', 'ObjectMap'], function(DomChangeListener, DomTraverse, EventHandler, ObjectMap) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       register: function() {},
-                       _checkVisibility: function() {},
-                       _hideDependentElement: function() {},
-                       _showDependentElement: function() {}
-               };
-               return Fake;
-       }
-       
-       var _pages = elBySelAll('input[name="pageIDs[]"]');
-       var _dependentElements = [];
-       var _pageIds = new ObjectMap();
-       var _hiddenElements = new ObjectMap();
-       
-       var _didInit = false;
-       
-       return {
-               register: function(dependentElement, pageIds) {
-                       _dependentElements.push(dependentElement);
-                       _pageIds.set(dependentElement, pageIds);
-                       _hiddenElements.set(dependentElement, []);
-                       
-                       if (!_didInit) {
-                               for (var i = 0, length = _pages.length; i < length; i++) {
-                                       _pages[i].addEventListener('change', this._checkVisibility.bind(this));
-                               }
-                               
-                               _didInit = true;
-                       }
-                       
-                       // remove the dependent element before submit if it is hidden
-                       DomTraverse.parentByTag(dependentElement, 'FORM').addEventListener('submit', function() {
-                               if (dependentElement.style.getPropertyValue('display') === 'none') {
-                                       dependentElement.remove();
-                               }
-                       });
-                       
-                       this._checkVisibility();
-               },
-               
-               /**
-                * Checks if only relevant pages are selected. If that is the case, the dependent
-                * element is shown, otherwise it is hidden.
-                * 
-                * @private
-                */
-               _checkVisibility: function() {
-                       var dependentElement, page, pageIds, checkedPageIds, irrelevantPageIds;
-                       
-                       depenentElementLoop: for (var i = 0, length = _dependentElements.length; i < length; i++) {
-                               dependentElement = _dependentElements[i];
-                               pageIds = _pageIds.get(dependentElement);
-                               
-                               checkedPageIds = [];
-                               for (var j = 0, length2 = _pages.length; j < length2; j++) {
-                                       page = _pages[j];
-                                       
-                                       if (page.checked) {
-                                               checkedPageIds.push(~~page.value);
-                                       }
-                               }
-                               
-                               irrelevantPageIds = checkedPageIds.filter(function(pageId) {
-                                       return pageIds.indexOf(pageId) === -1;
-                               });
-                               
-                               if (!checkedPageIds.length || irrelevantPageIds.length) {
-                                       this._hideDependentElement(dependentElement);
-                               }
-                               else {
-                                       this._showDependentElement(dependentElement);
-                               }
-                       }
-                       
-                       EventHandler.fire('com.woltlab.wcf.pageConditionDependence', 'checkVisivility');
-               },
-               
-               /**
-                * Hides all elements that depend on the given element.
-                * 
-                * @param       {HTMLElement}   dependentElement
-                */
-               _hideDependentElement: function(dependentElement) {
-                       elHide(dependentElement);
-                       
-                       var hiddenElements = _hiddenElements.get(dependentElement);
-                       for (var i = 0, length = hiddenElements.length; i < length; i++) {
-                               elHide(hiddenElements[i]);
-                       }
-                       
-                       _hiddenElements.set(dependentElement, []);
-               },
-               
-               /**
-                * Shows all elements that depend on the given element.
-                * 
-                * @param       {HTMLElement}   dependentElement
-                */
-               _showDependentElement: function(dependentElement) {
-                       elShow(dependentElement);
-                       
-                       // make sure that all parent elements are also visible
-                       var parentNode = dependentElement;
-                       while ((parentNode = parentNode.parentNode) && parentNode instanceof Element) {
-                               if (parentNode.style.getPropertyValue('display') === 'none') {
-                                       _hiddenElements.get(dependentElement).push(parentNode);
-                               }
-                               
-                               elShow(parentNode);
-                       }
-               }
-       };
-});
-
-/**
- * Map route planner based on Google Maps.
- *
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Controller/Map/Route/Planner
- */
-define('WoltLabSuite/Core/Controller/Map/Route/Planner',[
-       'Dom/Traverse',
-       'Dom/Util',
-       'Language',
-       'Ui/Dialog',
-       'WoltLabSuite/Core/Ajax/Status'
-], function(
-       DomTraverse,
-       DomUtil,
-       Language,
-       UiDialog,
-       AjaxStatus
-) {
-       /**
-        * @constructor
-        */
-       function Planner(buttonId, destination) {
-               this._button = elById(buttonId);
-               if (this._button === null) {
-                       throw new Error("Unknown button with id '" + buttonId + "'");
-               }
-               
-               this._button.addEventListener('click', this._openDialog.bind(this));
-               
-               this._destination = destination;
-       }
-       Planner.prototype = {
-               /**
-                * Sets up the route planner dialog.
-                */
-               _dialogSetup: function() {
-                       return {
-                               id: this._button.id + 'Dialog',
-                               options: {
-                                       onShow: this._initDialog.bind(this),
-                                       title: Language.get('wcf.map.route.planner')
-                               },
-                               source: '<div class="googleMapsDirectionsContainer" style="display: none;">' +
-                                               '<div class="googleMap"></div>' +
-                                               '<div class="googleMapsDirections"></div>' +
-                                       '</div>' +
-                                       '<small class="googleMapsDirectionsGoogleLinkContainer"><a href="' + this._getGoogleMapsLink() + '" class="googleMapsDirectionsGoogleLink" target="_blank" style="display: none;">' + Language.get('wcf.map.route.viewOnGoogleMaps') + '</a></small>' +
-                                       '<dl>' +
-                                               '<dt>' + Language.get('wcf.map.route.origin') + '</dt>' +
-                                               '<dd><input type="text" name="origin" class="long" autofocus /></dd>' +
-                                       '</dl>' +
-                                       '<dl style="display: none;">' +
-                                               '<dt>' + Language.get('wcf.map.route.travelMode') + '</dt>' +
-                                               '<dd>' +
-                                                       '<select name="travelMode">' +
-                                                               '<option value="driving">' + Language.get('wcf.map.route.travelMode.driving') + '</option>' + 
-                                                               '<option value="walking">' + Language.get('wcf.map.route.travelMode.walking') + '</option>' + 
-                                                               '<option value="bicycling">' + Language.get('wcf.map.route.travelMode.bicycling') + '</option>' +
-                                                               '<option value="transit">' + Language.get('wcf.map.route.travelMode.transit') + '</option>' +
-                                                       '</select>' +
-                                               '</dd>' +
-                                       '</dl>'
-                       }
-               },
-               
-               /**
-                * Calculates the route based on the given result of a location search.
-                * 
-                * @param       {object}        data
-                */
-               _calculateRoute: function(data) {
-                       var dialog = UiDialog.getDialog(this).dialog;
-                       
-                       if (data.label) {
-                               this._originInput.value = data.label;
-                       }
-                       
-                       if (this._map === undefined) {
-                               this._map = new google.maps.Map(elByClass('googleMap', dialog)[0], {
-                                       disableDoubleClickZoom: WCF.Location.GoogleMaps.Settings.get('disableDoubleClickZoom'),
-                                       draggable: WCF.Location.GoogleMaps.Settings.get('draggable'),
-                                       mapTypeId: google.maps.MapTypeId.ROADMAP,
-                                       scaleControl: WCF.Location.GoogleMaps.Settings.get('scaleControl'),
-                                       scrollwheel: WCF.Location.GoogleMaps.Settings.get('scrollwheel')
-                               });
-                               
-                               this._directionsService = new google.maps.DirectionsService();
-                               this._directionsRenderer = new google.maps.DirectionsRenderer();
-                               
-                               this._directionsRenderer.setMap(this._map);
-                               this._directionsRenderer.setPanel(elByClass('googleMapsDirections', dialog)[0]);
-                               
-                               this._googleLink = elByClass('googleMapsDirectionsGoogleLink', dialog)[0];
-                       }
-                       
-                       var request = {
-                               destination: this._destination,
-                               origin: data.location,
-                               provideRouteAlternatives: true,
-                               travelMode: google.maps.TravelMode[this._travelMode.value.toUpperCase()]
-                       };
-                       
-                       AjaxStatus.show();
-                       this._directionsService.route(request, this._setRoute.bind(this));
-                       
-                       elAttr(this._googleLink, 'href', this._getGoogleMapsLink(data.location, this._travelMode.value));
-                       
-                       this._lastOrigin = data.location;
-               },
-               
-               /**
-                * Returns the Google Maps link based on the given optional directions origin
-                * and optional travel mode.
-                * 
-                * @param       {google.maps.LatLng}    origin
-                * @param       {string}                travelMode
-                * @return      {string}
-                */
-               _getGoogleMapsLink: function(origin, travelMode) {
-                       if (origin) {
-                               var link = 'https://www.google.com/maps/dir/?api=1' +
-                                               '&origin=' + origin.lat() + ',' + origin.lng() + '' +
-                                               '&destination=' + this._destination.lat() + ',' + this._destination.lng();
-                               
-                               if (travelMode) {
-                                       link += '&travelmode=' + travelMode;
-                               }
-                               
-                               return link;
-                       }
-                       
-                       return 'https://www.google.com/maps/search/?api=1&query=' + this._destination.lat() + ',' + this._destination.lng();
-               },
-               
-               /**
-                * Initializes the route planning dialog.
-                */
-               _initDialog: function() {
-                       if (!this._didInitDialog) {
-                               var dialog = UiDialog.getDialog(this).dialog;
-                               
-                               // make input element a location search
-                               this._originInput = elBySel('input[name="origin"]', dialog);
-                               new WCF.Location.GoogleMaps.LocationSearch(this._originInput, this._calculateRoute.bind(this));
-                               
-                               this._travelMode = elBySel('select[name="travelMode"]', dialog);
-                               this._travelMode.addEventListener('change', this._updateRoute.bind(this));
-                               
-                               this._didInitDialog = true;
-                       }
-               },
-               
-               /**
-                * Opens the route planning dialog.
-                */
-               _openDialog: function() {
-                       UiDialog.open(this);
-               },
-               
-               /**
-                * Handles the response of the direction service.
-                * 
-                * @param       {object}        result
-                * @param       {string}        status
-                */
-               _setRoute: function(result, status) {
-                       AjaxStatus.hide();
-                       
-                       if (status === 'OK') {
-                               elShow(this._map.getDiv().parentNode);
-                               
-                               google.maps.event.trigger(this._map, 'resize');
-                               
-                               this._directionsRenderer.setDirections(result);
-                               
-                               elShow(DomTraverse.parentByTag(this._travelMode, 'DL'));
-                               elShow(this._googleLink);
-                               
-                               elInnerError(this._originInput, false);
-                       }
-                       else {
-                               // map irrelevant errors to not found error
-                               if (status !== 'OVER_QUERY_LIMIT' && status !== 'REQUEST_DENIED') {
-                                       status = 'NOT_FOUND';
-                               }
-                               
-                               elInnerError(this._originInput, Language.get('wcf.map.route.error.' + status.toLowerCase()));
-                       }
-               },
-               
-               /**
-                * Updates the route after the travel mode has been changed.
-                */
-               _updateRoute: function() {
-                       this._calculateRoute({
-                               location: this._lastOrigin
-                       });
-               }
-       };
-       
-       return Planner;
-});
-
-/**
- * Handles email notification type for user notification settings.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Controller/User/Notification/Settings
- */
-define('WoltLabSuite/Core/Controller/User/Notification/Settings',['Dictionary', 'Language', 'Dom/Traverse', 'Ui/SimpleDropdown'], function(Dictionary, Language, DomTraverse, UiSimpleDropdown) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       setup: function() {},
-                       _initGroup: function() {},
-                       _click: function() {},
-                       _createDropdown: function() {},
-                       _selectType: function() {}
-               };
-               return Fake;
-       }
-       
-       var _data = new Dictionary();
-       
-       var _callbackClick = null;
-       var _callbackSelectType = null;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Controller/User/Notification/Settings
-        */
-       var ControllerUserNotificationSettings = {
-               /**
-                * Binds event listeners for all notifications supporting emails.
-                */
-               setup: function() {
-                       _callbackClick = this._click.bind(this);
-                       _callbackSelectType = this._selectType.bind(this);
-                       
-                       var group, mailSetting, groups = elBySelAll('#notificationSettings .flexibleButtonGroup');
-                       for (var i = 0, length = groups.length; i < length; i++) {
-                               group = groups[i];
-                               
-                               mailSetting = elBySel('.notificationSettingsEmail', group);
-                               if (mailSetting === null) {
-                                       continue;
-                               }
-                               
-                               this._initGroup(group, mailSetting);
-                       }
-               },
-               
-               /**
-                * Initializes a setting.
-                * 
-                * @param       {Element}       group           button group element
-                * @param       {Element}       mailSetting     mail settings element
-                */
-               _initGroup: function(group, mailSetting) {
-                       var groupId = ~~elData(group, 'object-id');
-                       
-                       var disabledNotification = elById('settings_' + groupId + '_disabled');
-                       disabledNotification.addEventListener(WCF_CLICK_EVENT, function() { mailSetting.classList.remove('active'); });
-                       var enabledNotification = elById('settings_' + groupId + '_enabled');
-                       enabledNotification.addEventListener(WCF_CLICK_EVENT, function() { mailSetting.classList.add('active'); });
-                       
-                       var mailValue = DomTraverse.childByTag(mailSetting, 'INPUT');
-                       
-                       var button = DomTraverse.childByTag(mailSetting, 'A');
-                       elData(button, 'object-id', groupId);
-                       button.addEventListener(WCF_CLICK_EVENT, _callbackClick);
-                       
-                       _data.set(groupId, {
-                               button: button,
-                               dropdownMenu: null,
-                               mailSetting: mailSetting,
-                               mailValue: mailValue
-                       });
-               },
-               
-               /**
-                * Creates and displays the email type dropdown.
-                * 
-                * @param       {Object}        event           event object
-                */
-               _click: function(event) {
-                       event.preventDefault();
-                       
-                       var button = event.currentTarget;
-                       var objectId = ~~elData(button, 'object-id');
-                       var data = _data.get(objectId);
-                       if (data.dropdownMenu === null) {
-                               data.dropdownMenu = this._createDropdown(objectId, data.mailValue.value);
-                               
-                               button.parentNode.classList.add('dropdown');
-                               button.parentNode.appendChild(data.dropdownMenu);
-                               
-                               UiSimpleDropdown.init(button, event);
-                       }
-                       else {
-                               var items = DomTraverse.childrenByTag(data.dropdownMenu, 'LI'), value = data.mailValue.value;
-                               for (var i = 0; i < 4; i++) {
-                                       items[i].classList[(elData(items[i], 'value') === value) ? 'add' : 'remove']('active');
-                               }
-                       }
-               },
-               
-               /**
-                * Creates the email type dropdown.
-                * 
-                * @param       {int}           objectId        notification event id
-                * @param       {string}        initialValue    initial email type
-                * @returns     {Element}       dropdown menu object
-                */
-               _createDropdown: function(objectId, initialValue) {
-                       var dropdownMenu = elCreate('ul');
-                       dropdownMenu.className = 'dropdownMenu';
-                       elData(dropdownMenu, 'object-id', objectId);
-                       
-                       var link, listItem, value, items = ['instant', 'daily', 'divider', 'none'];
-                       for (var i = 0; i < 4; i++) {
-                               value = items[i];
-                               
-                               listItem = elCreate('li');
-                               if (value === 'divider') {
-                                       listItem.className = 'dropdownDivider';
-                               }
-                               else {
-                                       link = elCreate('a');
-                                       link.textContent = Language.get('wcf.user.notification.mailNotificationType.' + value);
-                                       listItem.appendChild(link);
-                                       elData(listItem, 'value', value);
-                                       listItem.addEventListener(WCF_CLICK_EVENT, _callbackSelectType);
-                                       
-                                       if (initialValue === value) {
-                                               listItem.className = 'active';
-                                       }
-                               }
-                               
-                               dropdownMenu.appendChild(listItem);
-                       }
-                       
-                       return dropdownMenu;
-               },
-               
-               /**
-                * Sets the selected email notification type.
-                * 
-                * @param       {Object}        event           event object
-                */
-               _selectType: function(event) {
-                       var value = elData(event.currentTarget, 'value');
-                       var groupId = ~~elData(event.currentTarget.parentNode, 'object-id');
-                       
-                       var data = _data.get(groupId);
-                       data.mailValue.value = value;
-                       elBySel('span.title', data.mailSetting).textContent = Language.get('wcf.user.notification.mailNotificationType.' + value);
-                       
-                       data.button.classList[(value === 'none') ? 'remove' : 'add']('yellow');
-                       data.button.classList[(value === 'none') ? 'remove' : 'add']('active');
-               }
-       };
-       
-       return ControllerUserNotificationSettings;
-});
-
-/**
- * Data handler for a captcha form builder field in an Ajax form.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Captcha
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Captcha',['Core', './Field', 'WoltLabSuite/Core/Controller/Captcha'], function(Core, FormBuilderField, Captcha) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function FormBuilderFieldCaptcha(fieldId) {
-               this.init(fieldId);
-       };
-       Core.inherit(FormBuilderFieldCaptcha, FormBuilderField, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#getData
-                */
-               _getData: function() {
-                       if (Captcha.has(this._fieldId)) {
-                               return Captcha.getData(this._fieldId);
-                       }
-                       
-                       return {};
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_readField
-                */
-               _readField: function() {
-                       // does nothing
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#destroy
-                */
-               destroy: function() {
-                       if (Captcha.has(this._fieldId)) {
-                               Captcha.delete(this._fieldId);
-                       }
-               }
-       });
-       
-       return FormBuilderFieldCaptcha;
-});
-
-/**
- * Data handler for a form builder field in an Ajax form represented by checkboxes.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Checkboxes
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Checkboxes',['Core', './Field'], function(Core, FormBuilderField) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function FormBuilderFieldCheckboxes(fieldId) {
-               this.init(fieldId);
-       };
-       Core.inherit(FormBuilderFieldCheckboxes, FormBuilderField, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_getData
-                */
-               _getData: function() {
-                       var data = {};
-                       
-                       data[this._fieldId] = [];
-                       
-                       for (var i = 0, length = this._fields.length; i < length; i++) {
-                               if (this._fields[i].checked) {
-                                       data[this._fieldId].push(this._fields[i].value);
-                               }
-                       }
-                       
-                       return data;
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_readField
-                */
-               _readField: function() {
-                       this._fields = elBySelAll('input[name="' + this._fieldId + '[]"]');
-               }
-       });
-       
-       return FormBuilderFieldCheckboxes;
-});
-
-/**
- * Data handler for a form builder field in an Ajax form that stores its value via a checkbox being
- * checked or not.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Checked
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Checked',['Core', './Field'], function(Core, FormBuilderField) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function FormBuilderFieldInput(fieldId) {
-               this.init(fieldId);
-       };
-       Core.inherit(FormBuilderFieldInput, FormBuilderField, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_getData
-                */
-               _getData: function() {
-                       var data = {};
-                       
-                       data[this._fieldId] = ~~this._field.checked;
-                       
-                       return data;
-               }
-       });
-       
-       return FormBuilderFieldInput;
-});
-
-/**
- * Data handler for a date form builder field in an Ajax form.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Date
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Date',['Core', 'WoltLabSuite/Core/Date/Picker', './Field'], function(Core, DatePicker, FormBuilderField) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function FormBuilderFieldDate(fieldId) {
-               this.init(fieldId);
-       };
-       Core.inherit(FormBuilderFieldDate, FormBuilderField, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_getData
-                */
-               _getData: function() {
-                       var data = {};
-                       
-                       data[this._fieldId] = DatePicker.getValue(this._field);
-                       
-                       return data;
-               }
-       });
-       
-       return FormBuilderFieldDate;
-});
-
-/**
- * Data handler for an item list form builder field in an Ajax form.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/ItemList
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/ItemList',['Core', './Field', 'WoltLabSuite/Core/Ui/ItemList/Static'], function(Core, FormBuilderField, UiItemListStatic) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function FormBuilderFieldItemList(fieldId) {
-               this.init(fieldId);
-       };
-       Core.inherit(FormBuilderFieldItemList, FormBuilderField, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_getData
-                */
-               _getData: function() {
-                       var data = {};
-                       data[this._fieldId] = [];
-                       
-                       var values = UiItemListStatic.getValues(this._fieldId);
-                       for (var i = 0, length = values.length; i < length; i++) {
-                               // TODO: data[this._fieldId] is an array but if code assumes object
-                               if (values[i].objectId) {
-                                       data[this._fieldId][values[i].objectId] = values[i].value;
-                               }
-                               else {
-                                       data[this._fieldId].push(values[i].value);
-                               }
-                       }
-                       
-                       return data;
-               }
-       });
-       
-       return FormBuilderFieldItemList;
-});
-
-/**
- * Data handler for a radio button form builder field in an Ajax form.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/RadioButton
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/RadioButton',['Core', './Field'], function(Core, FormBuilderField) {
-       "use strict";
-
-       /**
-        * @constructor
-        */
-       function FormBuilderFieldRadioButton(fieldId) {
-               this.init(fieldId);
-       };
-       Core.inherit(FormBuilderFieldRadioButton, FormBuilderField, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#getData
-                */
-               _getData: function() {
-                       var data = {};
-                       
-                       for (var i = 0, length = this._fields.length; i < length; i++) {
-                               if (this._fields[i].checked) {
-                                       data[this._fieldId] = this._fields[i].value;
-                                       break;
-                               }
-                       }
-                       
-                       return data;
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_readField
-                */
-               _readField: function() {
-                       this._fields = elBySelAll('input[name=' + this._fieldId + ']');
-               },
-       });
-       
-       return FormBuilderFieldRadioButton;
-});
-
-/**
- * Data handler for a simple acl form builder field in an Ajax form.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/SimpleAcl
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/SimpleAcl',['Core', './Field'], function(Core, FormBuilderField) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function FormBuilderFieldSimpleAcl(fieldId) {
-               this.init(fieldId);
-       };
-       Core.inherit(FormBuilderFieldSimpleAcl, FormBuilderField, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_getData
-                */
-               _getData: function() {
-                       var groupIds = [];
-                       elBySelAll('input[name="' + this._fieldId + '[group][]"]', undefined, function(input) {
-                               groupIds.push(~~input.value);
-                       });
-                       
-                       var usersIds = [];
-                       elBySelAll('input[name="' + this._fieldId + '[user][]"]', undefined, function(input) {
-                               usersIds.push(~~input.value);
-                       });
-                       
-                       var data = {};
-                       
-                       data[this._fieldId] = {
-                               group: groupIds,
-                               user: usersIds
-                       };
-                       
-                       return data;
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_readField
-                */
-               _readField: function() {
-                       // does nothing
-               }
-       });
-       
-       return FormBuilderFieldSimpleAcl;
-});
-
-/**
- * Data handler for a tag form builder field in an Ajax form.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Tag
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Tag',['Core', './Field', 'WoltLabSuite/Core/Ui/ItemList'], function(Core, FormBuilderField, UiItemList) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function FormBuilderFieldTag(fieldId) {
-               this.init(fieldId);
-       };
-       Core.inherit(FormBuilderFieldTag, FormBuilderField, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_getData
-                */
-               _getData: function() {
-                       var data = {};
-                       data[this._fieldId] = [];
-                       
-                       var values = UiItemList.getValues(this._fieldId);
-                       for (var i = 0, length = values.length; i < length; i++) {
-                               data[this._fieldId].push(values[i].value);
-                       }
-                       
-                       return data;
-               }
-       });
-       
-       return FormBuilderFieldTag;
-});
-
-/**
- * Data handler for a user form builder field in an Ajax form.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/User
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/User',['Core', './Field', 'WoltLabSuite/Core/Ui/ItemList'], function(Core, FormBuilderField, UiItemList) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function FormBuilderFieldUser(fieldId) {
-               this.init(fieldId);
-       };
-       Core.inherit(FormBuilderFieldUser, FormBuilderField, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_getData
-                */
-               _getData: function() {
-                       var values = UiItemList.getValues(this._fieldId);
-                       var usernames = [];
-                       for (var i = 0, length = values.length; i < length; i++) {
-                               if (values[i].objectId) {
-                                       usernames.push(values[i].value);
-                               }
-                       }
-                       
-                       var data = {};
-                       data[this._fieldId] = usernames.join(',');
-                       
-                       return data;
-               }
-       });
-       
-       return FormBuilderFieldUser;
-});
-
-/**
- * Data handler for a form builder field in an Ajax form that stores its value in an input's value
- * attribute.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Value
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Value',['Core', './Field'], function(Core, FormBuilderField) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function FormBuilderFieldValue(fieldId) {
-               this.init(fieldId);
-       };
-       Core.inherit(FormBuilderFieldValue, FormBuilderField, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_getData
-                */
-               _getData: function() {
-                       var data = {};
-                       
-                       data[this._fieldId] = this._field.value;
-                       
-                       return data;
-               }
-       });
-       
-       return FormBuilderFieldValue;
-});
-
-/**
- * Data handler for an i18n form builder field in an Ajax form that stores its value in an input's
- * value attribute.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/ValueI18n
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/ValueI18n',['Core', './Field', 'WoltLabSuite/Core/Language/Input'], function(Core, FormBuilderField, LanguageInput) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function FormBuilderFieldValueI18n(fieldId) {
-               this.init(fieldId);
-       };
-       Core.inherit(FormBuilderFieldValueI18n, FormBuilderField, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#_getData
-                */
-               _getData: function() {
-                       var data = {};
-                       
-                       var values = LanguageInput.getValues(this._fieldId);
-                       if (values.size > 1) {
-                               data[this._fieldId + '_i18n'] = values.toObject();
-                       }
-                       else {
-                               data[this._fieldId] = values.get(0);
-                       }
-                       
-                       return data;
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#destroy
-                */
-               destroy: function() {
-                       LanguageInput.unregister(this._fieldId);
-               }
-       });
-       
-       return FormBuilderFieldValueI18n;
-});
-
-/**
- * Handles the comment response add feature.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Comment/Add
- */
-define('WoltLabSuite/Core/Ui/Comment/Response/Add',[
-       'Core', 'Language', 'Dom/ChangeListener', 'Dom/Util', 'Dom/Traverse', 'Ui/Notification',  'WoltLabSuite/Core/Ui/Comment/Add'
-],
-function(
-       Core, Language, DomChangeListener, DomUtil, DomTraverse, UiNotification, UiCommentAdd
-) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       getContainer: function() {},
-                       getContent: function() {},
-                       setContent: function() {},
-                       _submitGuestDialog: function() {},
-                       _submit: function() {},
-                       _getParameters: function () {},
-                       _validate: function() {},
-                       throwError: function() {},
-                       _showLoadingOverlay: function() {},
-                       _hideLoadingOverlay: function() {},
-                       _reset: function() {},
-                       _handleError: function() {},
-                       _getEditor: function() {},
-                       _insertMessage: function() {},
-                       _ajaxSuccess: function() {},
-                       _ajaxFailure: function() {},
-                       _ajaxSetup: function() {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * @constructor
-        */
-       function UiCommentResponseAdd(container, options) { this.init(container, options); }
-       Core.inherit(UiCommentResponseAdd, UiCommentAdd, {
-               init: function (container, options) {
-                       UiCommentResponseAdd._super.prototype.init.call(this, container);
-                       
-                       this._options = Core.extend({
-                               callbackInsert: null
-                       }, options);
-               },
-               
-               /**
-                * Returns the editor container for placement or `null` if the editor is busy.
-                * 
-                * @return      {(Element|null)}
-                */
-               getContainer: function() {
-                       return (this._isBusy) ? null : this._container;
-               },
-               
-               /**
-                * Retrieves the current content from the editor.
-                * 
-                * @return      {string}
-                */
-               getContent: function () {
-                       return window.jQuery(this._textarea).redactor('code.get');
-               },
-               
-               /**
-                * Sets the content and places the caret at the end of the editor.
-                * 
-                * @param       {string}        html
-                */
-               setContent: function (html) {
-                       window.jQuery(this._textarea).redactor('code.set', html);
-                       window.jQuery(this._textarea).redactor('WoltLabCaret.endOfEditor');
-                       
-                       // the error message can appear anywhere in the container, not exclusively after the textarea
-                       var innerError = elBySel('.innerError', this._textarea.parentNode);
-                       if (innerError !== null) elRemove(innerError);
-                       
-                       this._content.classList.remove('collapsed');
-                       this._focusEditor();
-               },
-               
-               _getParameters: function () {
-                       var parameters = UiCommentResponseAdd._super.prototype._getParameters.call(this);
-                       parameters.data.commentID = ~~elData(this._container.closest('.comment'), 'object-id');
-                       
-                       return parameters;
-               },
-               
-               _insertMessage: function(data) {
-                       var commentContent = DomTraverse.childByClass(this._container.parentNode, 'commentContent');
-                       var responseList = commentContent.nextElementSibling;
-                       if (responseList === null || !responseList.classList.contains('commentResponseList')) {
-                               responseList = elCreate('ul');
-                               responseList.className = 'containerList commentResponseList';
-                               elData(responseList, 'responses', 0);
-                               
-                               commentContent.parentNode.insertBefore(responseList, commentContent.nextSibling);
-                       }
-                       
-                       // insert HTML
-                       //noinspection JSCheckFunctionSignatures
-                       DomUtil.insertHtml(data.returnValues.template, responseList, 'append');
-                       
-                       UiNotification.show(Language.get('wcf.global.success.add'));
-                       
-                       DomChangeListener.trigger();
-                       
-                       // reset editor
-                       window.jQuery(this._textarea).redactor('code.set', '');
-                       
-                       if (this._options.callbackInsert !== null) this._options.callbackInsert();
-                       
-                       // update counter
-                       elData(responseList, 'responses', responseList.children.length);
-                       
-                       return responseList.lastElementChild;
-               },
-               
-               _ajaxSetup: function() {
-                       var data = UiCommentResponseAdd._super.prototype._ajaxSetup.call(this);
-                       data.data.actionName = 'addResponse';
-                       
-                       return data;
-               }
-       });
-       
-       return UiCommentResponseAdd;
-});
-
-/**
- * Provides editing support for comment responses.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Comment/Response/Edit
- */
-define(
-       'WoltLabSuite/Core/Ui/Comment/Response/Edit',[
-               'Ajax',         'Core',            'Dictionary',          'Environment',
-               'EventHandler', 'Language',        'List',                'Dom/ChangeListener', 'Dom/Traverse',
-               'Dom/Util',     'Ui/Notification', 'Ui/ReusableDropdown', 'WoltLabSuite/Core/Ui/Scroll', 'WoltLabSuite/Core/Ui/Comment/Edit'
-       ],
-       function(
-               Ajax,            Core,              Dictionary,            Environment,
-               EventHandler,    Language,          List,                  DomChangeListener,    DomTraverse,
-               DomUtil,         UiNotification,    UiReusableDropdown,    UiScroll, UiCommentEdit
-       )
-{
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       init: function() {},
-                       rebuild: function() {},
-                       _click: function() {},
-                       _prepare: function() {},
-                       _showEditor: function() {},
-                       _restoreMessage: function() {},
-                       _save: function() {},
-                       _validate: function() {},
-                       throwError: function() {},
-                       _showMessage: function() {},
-                       _hideEditor: function() {},
-                       _restoreEditor: function() {},
-                       _destroyEditor: function() {},
-                       _getEditorId: function() {},
-                       _getObjectId: function() {},
-                       _ajaxFailure: function() {},
-                       _ajaxSuccess: function() {},
-                       _ajaxSetup: function() {}
-               };
-               return Fake;
-       }
-       
-       /**
-        * @constructor
-        */
-       function UiCommentResponseEdit(container) { this.init(container); }
-       Core.inherit(UiCommentResponseEdit, UiCommentEdit, {
-               /**
-                * Initializes the comment edit manager.
-                * 
-                * @param       {Element}       container       container element
-                */
-               init: function(container) {
-                       this._activeElement = null;
-                       this._callbackClick = null;
-                       this._container = container;
-                       this._editorContainer = null;
-                       this._responses = new List();
-                       
-                       this.rebuild();
-                       
-                       DomChangeListener.add('Ui/Comment/Response/Edit_' + DomUtil.identify(this._container), this.rebuild.bind(this));
-               },
-               
-               /**
-                * Initializes each applicable message, should be called whenever new
-                * messages are being displayed.
-                */
-               rebuild: function() {
-                       elBySelAll('.commentResponse', this._container, (function (response) {
-                               if (this._responses.has(response)) {
-                                       return;
-                               }
-                               
-                               if (elDataBool(response, 'can-edit')) {
-                                       var button = elBySel('.jsCommentResponseEditButton', response);
-                                       if (button !== null) {
-                                               if (this._callbackClick === null) {
-                                                       this._callbackClick = this._click.bind(this);
-                                               }
-                                               
-                                               button.addEventListener(WCF_CLICK_EVENT, this._callbackClick);
-                                       }
-                               }
-                               
-                               this._responses.add(response);
-                       }).bind(this));
-               },
-               
-               /**
-                * Handles clicks on the edit button.
-                *
-                * @param       {?Event}        event           event object
-                * @protected
-                */
-               _click: function(event) {
-                       event.preventDefault();
-                       
-                       if (this._activeElement === null) {
-                               this._activeElement = event.currentTarget.closest('.commentResponse');
-                               
-                               this._prepare();
-                               
-                               Ajax.api(this, {
-                                       actionName: 'beginEdit',
-                                       objectIDs: [this._getObjectId(this._activeElement)]
-                               });
-                       }
-                       else {
-                               UiNotification.show('wcf.message.error.editorAlreadyInUse', null, 'warning');
-                       }
-               },
-               
-               /**
-                * Prepares the message for editor display.
-                * 
-                * @protected
-                */
-               _prepare: function() {
-                       this._editorContainer = elCreate('div');
-                       this._editorContainer.className = 'commentEditorContainer';
-                       this._editorContainer.innerHTML = '<span class="icon icon48 fa-spinner"></span>';
-                       
-                       var content = elBySel('.commentResponseContent', this._activeElement);
-                       content.insertBefore(this._editorContainer, content.firstChild);
-               },
-               
-               /**
-                * Shows the update message.
-                * 
-                * @param       {Object}        data            ajax response data
-                * @protected
-                */
-               _showMessage: function(data) {
-                       // set new content
-                       //noinspection JSCheckFunctionSignatures
-                       DomUtil.setInnerHtml(elBySel('.commentResponseContent .userMessage', this._editorContainer.parentNode), data.returnValues.message);
-                       
-                       this._restoreMessage();
-                       
-                       UiNotification.show();
-               },
-               
-               /**
-                * Returns the unique editor id.
-                * 
-                * @return      {string}        editor id
-                * @protected
-                */
-               _getEditorId: function() {
-                       return 'commentResponseEditor' + this._getObjectId(this._activeElement);
-               },
-               
-               _ajaxSetup: function() {
-                       var objectTypeId = ~~elData(this._container, 'object-type-id');
-                       
-                       return {
-                               data: {
-                                       className: 'wcf\\data\\comment\\response\\CommentResponseAction',
-                                       parameters: {
-                                               data: {
-                                                       objectTypeID: objectTypeId
-                                               }
-                                       }
-                               },
-                               silent: true
-                       };
-               }
-       });
-       
-       return UiCommentResponseEdit;
-});
-
-/**
- * Manages the sticky page header.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Page/Header/Fixed
- */
-define('WoltLabSuite/Core/Ui/Page/Header/Fixed',['Core', 'EventHandler', 'Ui/Alignment', 'Ui/CloseOverlay', 'Ui/SimpleDropdown', 'Ui/Screen'], function(Core, EventHandler, UiAlignment, UiCloseOverlay, UiSimpleDropdown, UiScreen) {
-       "use strict";
-       
-       var _pageHeader, _pageHeaderContainer, _pageHeaderPanel, _pageHeaderSearch, _searchInput, _topMenu, _userPanelSearchButton;
-       var _isMobile = false;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Page/Header/Fixed
-        */
-       return {
-               /**
-                * Initializes the sticky page header handler.
-                */
-               init: function() {
-                       _pageHeader = elById('pageHeader');
-                       _pageHeaderContainer = elById('pageHeaderContainer');
-                       
-                       this._initSearchBar();
-                       
-                       UiScreen.on('screen-md-down', {
-                               match: function () { _isMobile = true; },
-                               unmatch: function () { _isMobile = false; },
-                               setup: function () { _isMobile = true; }
-                       });
-                       
-                       EventHandler.add('com.woltlab.wcf.Search', 'close', this._closeSearchBar.bind(this));
-               },
-               
-               /**
-                * Provides the collapsible search bar.
-                * 
-                * @protected
-                */
-               _initSearchBar: function() {
-                       _pageHeaderSearch = elById('pageHeaderSearch');
-                       _pageHeaderSearch.addEventListener(WCF_CLICK_EVENT, function(event) { event.stopPropagation(); });
-                       
-                       _pageHeaderPanel = elById('pageHeaderPanel');
-                       _searchInput = elById('pageHeaderSearchInput');
-                       _topMenu = elById('topMenu');
-                       
-                       _userPanelSearchButton = elById('userPanelSearchButton');
-                       _userPanelSearchButton.addEventListener(WCF_CLICK_EVENT, (function(event) {
-                               event.preventDefault();
-                               event.stopPropagation();
-                               
-                               if (_pageHeader.classList.contains('searchBarOpen')) {
-                                       this._closeSearchBar();
-                               }
-                               else {
-                                       this._openSearchBar();
-                               }
-                       }).bind(this));
-                       
-                       UiCloseOverlay.add('WoltLabSuite/Core/Ui/Page/Header/Fixed', (function() {
-                               if (_pageHeader.classList.contains('searchBarForceOpen')) return;
-                               
-                               this._closeSearchBar();
-                       }).bind(this));
-                       
-                       EventHandler.add('com.woltlab.wcf.MainMenuMobile', 'more', (function(data) {
-                               if (data.identifier === 'com.woltlab.wcf.search') {
-                                       data.handler.close(true);
-                                       
-                                       Core.triggerEvent(_userPanelSearchButton, WCF_CLICK_EVENT);
-                               }
-                       }).bind(this));
-               },
-               
-               /**
-                * Opens the search bar.
-                * 
-                * @protected
-                */
-               _openSearchBar: function() {
-                       window.WCF.Dropdown.Interactive.Handler.closeAll();
-                       
-                       _pageHeader.classList.add('searchBarOpen');
-                       _userPanelSearchButton.parentNode.classList.add('open');
-                       
-                       if (!_isMobile) {
-                               // calculate value for `right` on desktop
-                               UiAlignment.set(_pageHeaderSearch, _topMenu, {
-                                       horizontal: 'right'
-                               });
-                       }
-                       
-                       _pageHeaderSearch.style.setProperty('top', _pageHeaderPanel.clientHeight + 'px', '');
-                       _searchInput.focus();
-                       window.setTimeout(function() {
-                               _searchInput.selectionStart = _searchInput.selectionEnd = _searchInput.value.length;
-                       }, 1);
-               },
-               
-               /**
-                * Closes the search bar.
-                * 
-                * @protected
-                */
-               _closeSearchBar: function () {
-                       _pageHeader.classList.remove('searchBarOpen');
-                       _userPanelSearchButton.parentNode.classList.remove('open');
-                       
-                       ['bottom', 'left', 'right', 'top'].forEach(function(propertyName) {
-                               _pageHeaderSearch.style.removeProperty(propertyName);
-                       });
-                       
-                       _searchInput.blur();
-                       
-                       // close the scope selection
-                       var scope = elBySel('.pageHeaderSearchType', _pageHeaderSearch);
-                       UiSimpleDropdown.close(scope.id);
-               }
-       };
-});
-
-/**
- * Suggestions for page object ids with external response data processing.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Page/Search/Input
- * @extends     module:WoltLabSuite/Core/Ui/Search/Input
- */
-define('WoltLabSuite/Core/Ui/Page/Search/Input',['Core', 'WoltLabSuite/Core/Ui/Search/Input'], function(Core, UiSearchInput) {
-       "use strict";
-       
-       /**
-        * @param       {Element}       element         input element
-        * @param       {Object=}       options         search options and settings
-        * @constructor
-        */
-       function UiPageSearchInput(element, options) { this.init(element, options); }
-       Core.inherit(UiPageSearchInput, UiSearchInput, {
-               init: function(element, options) {
-                       options = Core.extend({
-                               ajax: {
-                                       className: 'wcf\\data\\page\\PageAction'
-                               },
-                               callbackSuccess: null
-                       }, options);
-                       
-                       if (typeof options.callbackSuccess !== 'function') {
-                               throw new Error("Expected a valid callback function for 'callbackSuccess'.");
-                       }
-                       
-                       UiPageSearchInput._super.prototype.init.call(this, element, options);
-                       
-                       this._pageId = 0;
-               },
-               
-               /**
-                * Sets the target page id.
-                * 
-                * @param       {int}   pageId  target page id
-                */
-               setPageId: function(pageId) {
-                       this._pageId = pageId;
-               },
-               
-               _getParameters: function(value) {
-                       var data = UiPageSearchInput._super.prototype._getParameters.call(this, value);
-                       
-                       data.objectIDs = [this._pageId];
-                       
-                       return data;
-               },
-               
-               _ajaxSuccess: function(data) {
-                       this._options.callbackSuccess(data);
-               }
-       });
-       
-       return UiPageSearchInput;
-});
-
-/**
- * Provides access to the lookup function of page handlers, allowing the user to search and
- * select page object ids.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Page/Search/Handler
- */
-define('WoltLabSuite/Core/Ui/Page/Search/Handler',['Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog', './Input'], function(Language, StringUtil, DomUtil, UiDialog, UiPageSearchInput) {
-       "use strict";
-       
-       var _callback = null;
-       var _searchInput = null;
-       var _searchInputLabel = null;
-       var _searchInputHandler = null;
-       var _resultList = null;
-       var _resultListContainer = null;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/Page/Search/Handler
-        */
-       return {
-               /**
-                * Opens the lookup overlay for provided page id.
-                * 
-                * @param       {int}           pageId                  page id
-                * @param       {string}        title                   dialog title
-                * @param       {function}      callback                callback function provided with the user-selected object id
-                * @param       {string?}       labelLanguageItem       optional language item name for the search input label
-                */
-               open: function (pageId, title, callback, labelLanguageItem) {
-                       _callback = callback;
-                       
-                       UiDialog.open(this);
-                       UiDialog.setTitle(this, title);
-                       
-                       if (labelLanguageItem) {
-                               _searchInputLabel.textContent = Language.get(labelLanguageItem);
-                       }
-                       else {
-                               _searchInputLabel.textContent = Language.get('wcf.page.pageObjectID.search.terms');
-                       }
-                       
-                       this._getSearchInputHandler().setPageId(pageId);
-               },
-               
-               /**
-                * Builds the result list.
-                * 
-                * @param       {Object}        data            AJAX response data
-                * @protected
-                */
-               _buildList: function(data) {
-                       this._resetList();
-                       
-                       // no matches
-                       if (!Array.isArray(data.returnValues) || data.returnValues.length === 0) {
-                               elInnerError(_searchInput, Language.get('wcf.page.pageObjectID.search.noResults'));
-                               
-                               return;
-                       }
-                       
-                       var image, item, listItem;
-                       for (var i = 0, length = data.returnValues.length; i < length; i++) {
-                               item = data.returnValues[i];
-                               image = item.image;
-                               if (/^fa-/.test(image)) {
-                                       image = '<span class="icon icon48 ' + image + ' pointer jsTooltip" title="' + Language.get('wcf.global.select') + '"></span>';
-                               }
-                               
-                               listItem = elCreate('li');
-                               elData(listItem, 'object-id', item.objectID);
-                               
-                               listItem.innerHTML = '<div class="box48">'
-                                       + image
-                                       + '<div>'
-                                               + '<div class="containerHeadline">'
-                                                       + '<h3><a href="' + StringUtil.escapeHTML(item.link) + '">' + StringUtil.escapeHTML(item.title) + '</a></h3>'
-                                                       + (item.description ? '<p>' + item.description + '</p>' : '')
-                                               + '</div>'
-                                       + '</div>'
-                               + '</div>';
-                               
-                               listItem.addEventListener(WCF_CLICK_EVENT, this._click.bind(this));
-                               
-                               _resultList.appendChild(listItem);
-                       }
-                       
-                       elShow(_resultListContainer);
-               },
-               
-               /**
-                * Resets the list and removes any error elements.
-                * 
-                * @protected
-                */
-               _resetList: function() {
-                       elInnerError(_searchInput, false);
-                       
-                       _resultList.innerHTML = '';
-                       
-                       elHide(_resultListContainer);
-               },
-               
-               /**
-                * Initializes the search input handler and returns the instance.
-                * 
-                * @returns     {UiPageSearchInput}     search input handler
-                * @protected
-                */
-               _getSearchInputHandler: function() {
-                       if (_searchInputHandler === null) {
-                               var callback = this._buildList.bind(this);
-                               _searchInputHandler = new UiPageSearchInput(elById('wcfUiPageSearchInput'), {
-                                       callbackSuccess: callback
-                               });
-                       }
-                       
-                       return _searchInputHandler;
-               },
-               
-               /**
-                * Handles clicks on the item unless the click occurred directly on a link.
-                * 
-                * @param       {Event}         event           event object
-                * @protected
-                */
-               _click: function(event) {
-                       if (event.target.nodeName === 'A') {
-                               return;
-                       }
-                       
-                       event.stopPropagation();
-                       
-                       _callback(elData(event.currentTarget, 'object-id'));
-                       UiDialog.close(this);
-               },
-               
-               _dialogSetup: function() {
-                       return {
-                               id: 'wcfUiPageSearchHandler',
-                               options: {
-                                       onShow: function() {
-                                               if (_searchInput === null) {
-                                                       _searchInput = elById('wcfUiPageSearchInput');
-                                                       _searchInputLabel = _searchInput.parentNode.previousSibling.childNodes[0];
-                                                       _resultList = elById('wcfUiPageSearchResultList');
-                                                       _resultListContainer = elById('wcfUiPageSearchResultListContainer');
-                                               }
-                                               
-                                               // clear search input
-                                               _searchInput.value = '';
-                                               
-                                               // reset results
-                                               elHide(_resultListContainer);
-                                               _resultList.innerHTML = '';
-                                               
-                                               _searchInput.focus();
-                                       },
-                                       title: ''
-                               },
-                               source: '<div class="section">'
-                                               + '<dl>'
-                                                       + '<dt><label for="wcfUiPageSearchInput">' + Language.get('wcf.page.pageObjectID.search.terms') + '</label></dt>'
-                                                       + '<dd>'
-                                                               + '<input type="text" id="wcfUiPageSearchInput" class="long">'
-                                                       + '</dd>'
-                                               + '</dl>'
-                                       + '</div>'
-                                       + '<section id="wcfUiPageSearchResultListContainer" class="section sectionContainerList">'
-                                               + '<header class="sectionHeader">'
-                                                       + '<h2 class="sectionTitle">' + Language.get('wcf.page.pageObjectID.search.results') + '</h2>'
-                                               + '</header>'
-                                               + '<ul id="wcfUiPageSearchResultList" class="containerList wcfUiPageSearchResultList"></ul>'
-                                       + '</section>'
-                       };
-               }
-       };
-});
-
-/**
- * Handles the reaction list in the user profile. 
- *
- * @author     Joshua Ruesweg
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/Reaction/Profile/Loader
- * @since       5.2
- */
-define('WoltLabSuite/Core/Ui/Reaction/Profile/Loader',['Ajax', 'Core', 'Language'], function(Ajax, Core, Language) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function UiReactionProfileLoader(userID, firstReactionTypeID) { this.init(userID, firstReactionTypeID); }
-       UiReactionProfileLoader.prototype = {
-               /**
-                * Initializes a new ReactionListLoader object.
-                *
-                * @param       integer         userID
-                */
-               init: function(userID, firstReactionTypeID) {
-                       this._container = elById('likeList');
-                       this._userID = userID;
-                       this._reactionTypeID = firstReactionTypeID;
-                       this._targetType = 'received';
-                       this._options = {
-                               parameters: []
-                       };
-                       
-                       if (!this._userID) {
-                               throw new Error("[WoltLabSuite/Core/Ui/Reaction/Profile/Loader] Invalid parameter 'userID' given.");
-                       }
-                       
-                       if (!this._reactionTypeID) {
-                               throw new Error("[WoltLabSuite/Core/Ui/Reaction/Profile/Loader] Invalid parameter 'firstReactionTypeID' given.");
-                       }
-                       
-                       var loadButtonList = elCreate('li');
-                       loadButtonList.className = 'likeListMore showMore';
-                       this._noMoreEntries = elCreate('small');
-                       this._noMoreEntries.innerHTML = Language.get('wcf.like.reaction.noMoreEntries');
-                       this._noMoreEntries.style.display = 'none';
-                       loadButtonList.appendChild(this._noMoreEntries);
-                       
-                       this._loadButton = elCreate('button');
-                       this._loadButton.className = 'small';
-                       this._loadButton.innerHTML = Language.get('wcf.like.reaction.more');
-                       this._loadButton.addEventListener(WCF_CLICK_EVENT, this._loadReactions.bind(this));
-                       this._loadButton.style.display = 'none';
-                       loadButtonList.appendChild(this._loadButton);
-                       this._container.appendChild(loadButtonList);
-                       
-                       if (elBySel('#likeList > li').length === 2) {
-                               this._noMoreEntries.style.display = '';
-                       }
-                       else {
-                               this._loadButton.style.display = '';
-                       }
-                       
-                       this._setupReactionTypeButtons();
-                       this._setupTargetTypeButtons();
-               },
-               
-               /**
-                * Set up the reaction type buttons. 
-                */
-               _setupReactionTypeButtons: function() {
-                       var element, elements = elBySelAll('#reactionType .button');
-                       for (var i = 0, length = elements.length; i < length; i++) {
-                               element = elements[i];
-                               element.addEventListener(WCF_CLICK_EVENT, this._changeReactionTypeValue.bind(this, ~~elData(element, 'reaction-type-id')));
-                       }
-               },
-               
-               /**
-                * Set up the target type buttons.
-                */
-               _setupTargetTypeButtons: function() {
-                       var element, elements = elBySelAll('#likeType .button');
-                       for (var i = 0, length = elements.length; i < length; i++) {
-                               element = elements[i];
-                               element.addEventListener(WCF_CLICK_EVENT, this._changeTargetType.bind(this, elData(element, 'like-type')));
-                       }
-               },
-               
-               /**
-                * Changes the reaction target type (given or received) and reload the entire element.
-                * 
-                * @param       {string}           targetType
-                */
-               _changeTargetType: function(targetType) {
-                       if (targetType !== 'given' && targetType !== 'received') {
-                               throw new Error("[WoltLabSuite/Core/Ui/Reaction/Profile/Loader] Invalid parameter 'targetType' given.");
-                       }
-                       
-                       if (targetType !== this._targetType) {
-                               // remove old active state
-                               elBySel('#likeType .button.active').classList.remove('active');
-                               
-                               // add active status to new button 
-                               elBySel('#likeType .button[data-like-type="'+ targetType +'"]').classList.add('active');
-                               
-                               this._targetType = targetType;
-                               this._reload();
-                       }
-               },
-               
-               /**
-                * Changes the reaction type value and reload the entire element. 
-                * 
-                * @param       {int}           reactionTypeID
-                */
-               _changeReactionTypeValue: function(reactionTypeID) {
-                       if (this._reactionTypeID !== reactionTypeID) {
-                               // remove old active state
-                               elBySel('#reactionType .button.active').classList.remove('active');
-                               
-                               // add active status to new button 
-                               elBySel('#reactionType .button[data-reaction-type-id="'+ reactionTypeID +'"]').classList.add('active');
-                               
-                               this._reactionTypeID = reactionTypeID;
-                               this._reload();
-                       }
-               },
-               
-               /**
-                * Handles reload.
-                */
-               _reload: function() {
-                       var elements = elBySelAll('#likeList > li:not(:first-child):not(:last-child)');
-                       
-                       for (var i = 0, length = elements.length; i < length; i++) {
-                               this._container.removeChild(elements[i]);
-                       }
-                       
-                       elData(this._container, 'last-like-time', 0);
-                       
-                       this._loadReactions();
-               },
-               
-               /**
-                * Load a list of reactions. 
-                */
-               _loadReactions: function() {
-                       this._options.parameters.userID = this._userID;
-                       this._options.parameters.lastLikeTime = elData(this._container, 'last-like-time');
-                       this._options.parameters.targetType = this._targetType;
-                       this._options.parameters.reactionTypeID = this._reactionTypeID;
-                       
-                       Ajax.api(this, {
-                               parameters: this._options.parameters
-                       });
-               },
-               
-               _ajaxSuccess: function(data) {
-                       if (data.returnValues.template) {
-                               elBySel('#likeList > li:nth-last-child(1)').insertAdjacentHTML('beforebegin', data.returnValues.template);
-                               
-                               elData(this._container, 'last-like-time', data.returnValues.lastLikeTime);
-                               this._noMoreEntries.style.display = 'none';
-                               this._loadButton.style.display = '';
-                       }
-                       else {
-                               this._noMoreEntries.style.display = '';
-                               this._loadButton.style.display = 'none';
-                       }
-               },
-               
-               _ajaxSetup: function() {
-                       return {
-                               data: {
-                                       actionName: 'load',
-                                       className: '\\wcf\\data\\reaction\\ReactionAction'
-                               }
-                       };
-               }
-       };
-       
-       return UiReactionProfileLoader;
-});
-
-define('WoltLabSuite/Core/Ui/User/Activity/Recent',['Ajax', 'Language', 'Dom/Util'], function(Ajax, Language, DomUtil) {
-       "use strict";
-       
-       function UiUserActivityRecent(containerId) { this.init(containerId); }
-       UiUserActivityRecent.prototype = {
-               init: function (containerId) {
-                       this._containerId = containerId;
-                       var container = elById(this._containerId);
-                       this._list = elBySel('.recentActivityList', container);
-                       
-                       var showMoreItem = elCreate('li');
-                       showMoreItem.className = 'showMore';
-                       if (this._list.childElementCount) {
-                               showMoreItem.innerHTML = '<button class="small">' + Language.get('wcf.user.recentActivity.more') + '</button>';
-                               showMoreItem.children[0].addEventListener(WCF_CLICK_EVENT, this._showMore.bind(this));
-                       }
-                       else {
-                               showMoreItem.innerHTML = '<small>' + Language.get('wcf.user.recentActivity.noMoreEntries') + '</small>';
-                       }
-                       
-                       this._list.appendChild(showMoreItem);
-                       this._showMoreItem = showMoreItem;
-                       
-                       elBySelAll('.jsRecentActivitySwitchContext .button', container, (function (button) {
-                               button.addEventListener(WCF_CLICK_EVENT, (function (event) {
-                                       event.preventDefault();
-                                       
-                                       if (!button.classList.contains('active')) {
-                                               this._switchContext();
-                                       }
-                               }).bind(this));
-                       }).bind(this));
-               },
-               
-               _showMore: function (event) {
-                       event.preventDefault();
-                       
-                       this._showMoreItem.children[0].disabled = true;
-                       
-                       Ajax.api(this, {
-                               actionName: 'load',
-                               parameters: {
-                                       boxID: ~~elData(this._list, 'box-id'),
-                                       filteredByFollowedUsers: elDataBool(this._list, 'filtered-by-followed-users'),
-                                       lastEventId: elData(this._list, 'last-event-id'),
-                                       lastEventTime: elData(this._list, 'last-event-time'),
-                                       userID: ~~elData(this._list, 'user-id')
-                               }
-                       });
-               },
-               
-               _switchContext: function() {
-                       Ajax.api(
-                               this,
-                               {
-                                       actionName: 'switchContext'
-                               },
-                               (function () {
-                                       window.location.hash = '#' + this._containerId;
-                                       window.location.reload();
-                               }).bind(this)
-                       );
-               },
-               
-               _ajaxSuccess: function(data) {
-                       if (data.returnValues.template) {
-                               DomUtil.insertHtml(data.returnValues.template, this._showMoreItem, 'before');
-                               
-                               elData(this._list, 'last-event-time', data.returnValues.lastEventTime);
-                               elData(this._list, 'last-event-id', data.returnValues.lastEventID);
-                               
-                               this._showMoreItem.children[0].disabled = false;
-                       }
-                       else {
-                               this._showMoreItem.innerHTML = '<small>' + Language.get('wcf.user.recentActivity.noMoreEntries') + '</small>';
-                       }
-               },
-               
-               _ajaxSetup: function () {
-                       return {
-                               data: {
-                                       className: 'wcf\\data\\user\\activity\\event\\UserActivityEventAction'
-                               }
-                       };
-               }
-       };
-       
-       return UiUserActivityRecent;
-});
-
-/**
- * Deletes the current user cover photo.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/User/CoverPhoto/Delete
- */
-define('WoltLabSuite/Core/Ui/User/CoverPhoto/Delete',['Ajax', 'EventHandler', 'Language', 'Ui/Confirmation', 'Ui/Notification'], function (Ajax, EventHandler, Language, UiConfirmation, UiNotification) {
-       "use strict";
-       
-       var _button;
-       var _userId = 0;
-       
-       /**
-        * @exports     WoltLabSuite/Core/Ui/User/CoverPhoto/Delete
-        */
-       return {
-               /**
-                * Initializes the delete handler and enables the delete button on upload.
-                */
-               init: function (userId) {
-                       _button = elBySel('.jsButtonDeleteCoverPhoto');
-                       _button.addEventListener(WCF_CLICK_EVENT, this._click.bind(this));
-                       _userId = userId;
-                       
-                       EventHandler.add('com.woltlab.wcf.user', 'coverPhoto', function (data) {
-                               if (typeof data.url === 'string' && data.url.length > 0) {
-                                       elShow(_button.parentNode);
-                               }
-                       });
-               },
-               
-               /**
-                * Handles clicks on the delete button.
-                * 
-                * @param {Event} event
-                * @protected
-                */
-               _click: function (event) {
-                       event.preventDefault();
-                       
-                       UiConfirmation.show({
-                               confirm: Ajax.api.bind(Ajax, this),
-                               message: Language.get('wcf.user.coverPhoto.delete.confirmMessage')
-                       });
-               },
-               
-               _ajaxSuccess: function (data) {
-                       elBySel('.userProfileCoverPhoto').style.setProperty('background-image', 'url(' + data.returnValues.url + ')', '');
-                       
-                       elHide(_button.parentNode);
-                       
-                       UiNotification.show();
-               },
-               
-               _ajaxSetup: function () {
-                       return {
-                               data: {
-                                       actionName: 'deleteCoverPhoto',
-                                       className: 'wcf\\data\\user\\UserProfileAction',
-                                       parameters: {
-                                               userID: _userId
-                                       }
-                               }
-                       };
-               }
-       };
-});
-
-/**
- * Uploads the user cover photo via AJAX.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/User/CoverPhoto/Upload
- */
-define('WoltLabSuite/Core/Ui/User/CoverPhoto/Upload',['Core', 'EventHandler', 'Upload', 'Ui/Notification', 'Ui/Dialog'], function(Core, EventHandler, Upload, UiNotification, UiDialog) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function UiUserCoverPhotoUpload(userId) {
-               Upload.call(this, 'coverPhotoUploadButtonContainer', 'coverPhotoUploadPreview', {
-                       action: 'uploadCoverPhoto',
-                       className: 'wcf\\data\\user\\UserProfileAction'
-               });
-               
-               this._userId = userId;
-       }
-       Core.inherit(UiUserCoverPhotoUpload, Upload, {
-               _getParameters: function() {
-                       return {
-                               userID: this._userId
-                       };
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Upload#_success
-                */
-               _success: function(uploadId, data) {
-                       // remove or display the error message
-                       elInnerError(this._button, data.returnValues.errorMessage);
-                       
-                       // remove the upload progress
-                       this._target.innerHTML = '';
-                       
-                       if (data.returnValues.url) {
-                               elBySel('.userProfileCoverPhoto').style.setProperty('background-image', 'url(' + data.returnValues.url + ')', '');
-                               
-                               UiDialog.close('userProfileCoverPhotoUpload');
-                               UiNotification.show();
-                               
-                               EventHandler.fire('com.woltlab.wcf.user', 'coverPhoto', {
-                                       url: data.returnValues.url
-                               });
-                       }
-               }
-       });
-       
-       return UiUserCoverPhotoUpload;
-});
-
-/**
- * Handles the user trophy dialog.
- *
- * @author     Joshua Ruesweg
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/User/Trophy/List
- */
-define('WoltLabSuite/Core/Ui/User/Trophy/List',['Ajax', 'Core', 'Dictionary', 'Dom/Util', 'Ui/Dialog', 'WoltLabSuite/Core/Ui/Pagination', 'Dom/ChangeListener', 'List'], function(Ajax, Core, Dictionary, DomUtil, UiDialog, UiPagination, DomChangeListener, List) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function UiUserTrophyList() { this.init(); }
-       UiUserTrophyList.prototype = {
-               /**
-                * Initializes the user trophy list.
-                */
-               init: function() {
-                       this._cache = new Dictionary();
-                       this._knownElements = new List();
-                       
-                       this._options = {
-                               className: 'wcf\\data\\user\\trophy\\UserTrophyAction',
-                               parameters: {}
-                       };
-                       
-                       this._rebuild();
-                       
-                       DomChangeListener.add('WoltLabSuite/Core/Ui/User/Trophy/List', this._rebuild.bind(this));
-               },
-               
-               /**
-                * Adds event userTrophyOverlayList elements.
-                */
-               _rebuild: function() {
-                       elBySelAll('.userTrophyOverlayList', undefined, (function (element) {
-                               if (!this._knownElements.has(element)) {
-                                       element.addEventListener(WCF_CLICK_EVENT, this._open.bind(this, elData(element, 'user-id')));
-                                       
-                                       this._knownElements.add(element);
-                               }
-                       }).bind(this));
-               },
-               
-               /**
-                * Opens the user trophy list for a specific user.
-                *
-                * @param       {int}           userId
-                * @param       {Event}         event           event object
-                */
-               _open: function(userId, event) {
-                       event.preventDefault();
-                       
-                       this._currentPageNo = 1;
-                       this._currentUser = userId;
-                       this._showPage();
-               },
-               
-               /**
-                * Shows the current or given page.
-                *
-                * @param       {int=}          pageNo          page number
-                */
-               _showPage: function(pageNo) {
-                       if (pageNo !== undefined) {
-                               this._currentPageNo = pageNo;
-                       }
-                       
-                       if (this._cache.has(this._currentUser)) {
-                               // validate pageNo
-                               if (this._cache.get(this._currentUser).get('pageCount') !== 0 && (this._currentPageNo < 1 || this._currentPageNo > this._cache.get(this._currentUser).get('pageCount'))) {
-                                       throw new RangeError("pageNo must be between 1 and " + this._cache.get(this._currentUser).get('pageCount') + " (" + this._currentPageNo + " given).");
-                               }
-                       }
-                       else {
-                               // init user page cache
-                               this._cache.set(this._currentUser, new Dictionary());
-                       }
-                       
-                       if (this._cache.get(this._currentUser).has(this._currentPageNo)) {
-                               var dialog = UiDialog.open(this, this._cache.get(this._currentUser).get(this._currentPageNo));
-                               UiDialog.setTitle('userTrophyListOverlay', this._cache.get(this._currentUser).get('title'));
-                               
-                               if (this._cache.get(this._currentUser).get('pageCount') > 1) {
-                                       var element = elBySel('.jsPagination', dialog.content);
-                                       if (element !== null) {
-                                               new UiPagination(element, {
-                                                       activePage: this._currentPageNo,
-                                                       maxPage: this._cache.get(this._currentUser).get('pageCount'),
-                                                       callbackSwitch: this._showPage.bind(this)
-                                               });
-                                       }
-                               }
-                       }
-                       else {
-                               this._options.parameters.pageNo = this._currentPageNo;
-                               this._options.parameters.userID = this._currentUser;
-                               
-                               Ajax.api(this, {
-                                       parameters: this._options.parameters
-                               });
-                       }
-               },
-               
-               _ajaxSuccess: function(data) {
-                       if (data.returnValues.pageCount !== undefined) {
-                               this._cache.get(this._currentUser).set('pageCount', ~~data.returnValues.pageCount);
-                       }
-                       
-                       this._cache.get(this._currentUser).set(this._currentPageNo, data.returnValues.template);
-                       this._cache.get(this._currentUser).set('title', data.returnValues.title);
-                       this._showPage();
-               },
-               
-               _ajaxSetup: function() {
-                       return {
-                               data: {
-                                       actionName: 'getGroupedUserTrophyList',
-                                       className: this._options.className
-                               }
-                       };
-               },
-               
-               _dialogSetup: function() {
-                       return {
-                               id: 'userTrophyListOverlay',
-                               options: {
-                                       title: ""
-                               },
-                               source: null
-                       };
-               }
-       };
-       
-       return UiUserTrophyList;
-});
-
-/**
- * Handles the JavaScript part of the label form field.
- * 
- * @author     Alexander Ebert, Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Controller/Label
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Controller/Label',['Core', 'Dom/Util', 'Language', 'Ui/SimpleDropdown'], function(Core, DomUtil, Language, UiSimpleDropdown) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function FormBuilderFieldLabel(fielId, labelId, options) {
-               this.init(fielId, labelId, options);
-       };
-       FormBuilderFieldLabel.prototype = {
-               /**
-                * Initializes the label form field.
-                * 
-                * @param       {string}        fieldId         id of the relevant form builder field
-                * @param       {integer}       labelId         id of the currently selected label
-                * @param       {object}        options         additional label options
-                */
-               init: function(fieldId, labelId, options) {
-                       this._formFieldContainer = elById(fieldId + 'Container');
-                       this._labelChooser = elByClass('labelChooser', this._formFieldContainer)[0];
-                       this._options = Core.extend({
-                               forceSelection: false,
-                               showWithoutSelection: false
-                       }, options);
-                       
-                       this._input = elCreate('input');
-                       this._input.type = 'hidden';
-                       this._input.id = fieldId;
-                       this._input.name = fieldId;
-                       this._input.value = ~~labelId;
-                       this._formFieldContainer.appendChild(this._input);
-                       
-                       var labelChooserId = DomUtil.identify(this._labelChooser);
-                       
-                       // init dropdown
-                       var dropdownMenu = UiSimpleDropdown.getDropdownMenu(labelChooserId);
-                       if (dropdownMenu === null) {
-                               UiSimpleDropdown.init(elByClass('dropdownToggle', this._labelChooser)[0]);
-                               dropdownMenu = UiSimpleDropdown.getDropdownMenu(labelChooserId);
-                       }
-                       
-                       var additionalOptionList = null;
-                       if (this._options.showWithoutSelection || !this._options.forceSelection) {
-                               additionalOptionList = elCreate('ul');
-                               dropdownMenu.appendChild(additionalOptionList);
-                               
-                               var dropdownDivider = elCreate('li');
-                               dropdownDivider.className = 'dropdownDivider';
-                               additionalOptionList.appendChild(dropdownDivider);
-                       }
-                       
-                       if (this._options.showWithoutSelection) {
-                               var listItem = elCreate('li');
-                               elData(listItem, 'label-id', -1);
-                               this._blockScroll(listItem);
-                               additionalOptionList.appendChild(listItem);
-                               
-                               var span = elCreate('span');
-                               listItem.appendChild(span);
-                               
-                               var label = elCreate('span');
-                               label.className = 'badge label';
-                               label.innerHTML = Language.get('wcf.label.withoutSelection');
-                               span.appendChild(label);
-                       }
-                       
-                       if (!this._options.forceSelection) {
-                               var listItem = elCreate('li');
-                               elData(listItem, 'label-id', 0);
-                               this._blockScroll(listItem);
-                               additionalOptionList.appendChild(listItem);
-                               
-                               var span = elCreate('span');
-                               listItem.appendChild(span);
-                               
-                               var label = elCreate('span');
-                               label.className = 'badge label';
-                               label.innerHTML = Language.get('wcf.label.none');
-                               span.appendChild(label);
-                       }
-                       
-                       elBySelAll('li:not(.dropdownDivider)', dropdownMenu, function(listItem) {
-                               listItem.addEventListener('click', this._click.bind(this));
-                               
-                               if (labelId) {
-                                       if (~~elData(listItem, 'label-id') === labelId) {
-                                               this._selectLabel(listItem);
-                                       }
-                               }
-                       }.bind(this));
-               },
-               
-               /**
-                * Blocks page scrolling for the given element.
-                * 
-                * @param       {HTMLElement}           element
-                */
-               _blockScroll: function(element) {
-                       element.addEventListener(
-                               'wheel',
-                               function(event) {
-                                       event.preventDefault();
-                               },
-                               {
-                                       passive: false
-                               }
-                       );
-               },
-               
-               /**
-                * Select a label after clicking on it.
-                * 
-                * @param       {Event}         event   click event in label selection dropdown
-                */
-               _click: function(event) {
-                       event.preventDefault();
-                       
-                       this._selectLabel(event.currentTarget, false);
-               },
-               
-               /**
-                * Selects the given label.
-                * 
-                * @param       {HTMLElement}   label
-                */
-               _selectLabel: function(label) {
-                       // save label
-                       var labelId = elData(label, 'label-id');
-                       if (!labelId) {
-                               labelId = 0;
-                       }
-                       
-                       // replace button with currently selected label
-                       var displayLabel = elBySel('span > span', label);
-                       var button = elBySel('.dropdownToggle > span', this._labelChooser);
-                       button.className = displayLabel.className;
-                       button.textContent = displayLabel.textContent;
-                       
-                       this._input.value = labelId;
-               }
-       };
-       
-       return FormBuilderFieldLabel;
-});
-
-/**
- * Handles the JavaScript part of the rating form field.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Controller/Rating
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Controller/Rating',['Dictionary', 'Environment'], function(Dictionary, Environment) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function FormBuilderFieldRating(fieldId, value, activeCssClasses, defaultCssClasses) {
-               this.init(fieldId, value, activeCssClasses, defaultCssClasses);
-       };
-       FormBuilderFieldRating.prototype = {
-               /**
-                * Initializes the rating form field.
-                * 
-                * @param       {string}        fieldId                 id of the relevant form builder field
-                * @param       {integer}       value                   current value of the field
-                * @param       {string[]}      activeCssClasses        CSS classes for the active state of rating elements
-                * @param       {string[]}      defaultCssClasses       CSS classes for the default state of rating elements
-                */
-               init: function(fieldId, value, activeCssClasses, defaultCssClasses) {
-                       this._field = elBySel('#' + fieldId + 'Container');
-                       if (this._field === null) {
-                               throw new Error("Unknown field with id '" + fieldId + "'");
-                       }
-                       
-                       this._input = elCreate('input');
-                       this._input.id = fieldId;
-                       this._input.name = fieldId;
-                       this._input.type = 'hidden';
-                       this._input.value = value;
-                       this._field.appendChild(this._input);
-                       
-                       this._activeCssClasses = activeCssClasses;
-                       this._defaultCssClasses = defaultCssClasses;
-                       
-                       this._ratingElements = new Dictionary();
-                       
-                       var ratingList = elBySel('.ratingList', this._field);
-                       ratingList.addEventListener('mouseleave', this._restoreRating.bind(this));
-                       
-                       elBySelAll('li', ratingList, function(listItem) {
-                               if (listItem.classList.contains('ratingMetaButton')) {
-                                       listItem.addEventListener('click', this._metaButtonClick.bind(this));
-                                       listItem.addEventListener('mouseenter', this._restoreRating.bind(this));
-                               }
-                               else {
-                                       this._ratingElements.set(~~elData(listItem, 'rating'), listItem);
-                                       
-                                       listItem.addEventListener('click', this._listItemClick.bind(this));
-                                       listItem.addEventListener('mouseenter', this._listItemMouseEnter.bind(this));
-                                       listItem.addEventListener('mouseleave', this._listItemMouseLeave.bind(this));
-                               }
-                       }.bind(this));
-               },
-               
-               /**
-                * Saves the rating associated with the clicked rating element.
-                * 
-                * @param       {Event}         event   rating element `click` event
-                */
-               _listItemClick: function(event) {
-                       this._input.value = ~~elData(event.currentTarget, 'rating');
-                       
-                       if (Environment.platform() !== 'desktop') {
-                               this._restoreRating();
-                       }
-               },
-               
-               /**
-                * Updates the rating UI when hovering over a rating element.
-                * 
-                * @param       {Event}         event   rating element `mouseenter` event
-                */
-               _listItemMouseEnter: function(event) {
-                       var currentRating = elData(event.currentTarget, 'rating');
-                       
-                       this._ratingElements.forEach(function(ratingElement, rating) {
-                               var icon = elByClass('icon', ratingElement)[0];
-                               
-                               this._toggleIcon(icon, ~~rating <= ~~currentRating);
-                       }.bind(this));
-               },
-               
-               /**
-                * Updates the rating UI when leaving a rating element by changing all rating elements
-                * to their default state.
-                */
-               _listItemMouseLeave: function() {
-                       this._ratingElements.forEach(function(ratingElement) {
-                               var icon = elByClass('icon', ratingElement)[0];
-                               
-                               this._toggleIcon(icon, false);
-                       }.bind(this));
-               },
-               
-               /**
-                * Handles clicks on meta buttons.
-                * 
-                * @param       {Event}         event   meta button `click` event
-                */
-               _metaButtonClick: function(event) {
-                       if (elData(event.currentTarget, 'action') === 'removeRating') {
-                               this._input.value = '';
-                               
-                               this._listItemMouseLeave();
-                       }
-               },
-               
-               /**
-                * Updates the rating UI by changing the rating elements to the stored rating state.
-                */
-               _restoreRating: function() {
-                       this._ratingElements.forEach(function(ratingElement, rating) {
-                               var icon = elByClass('icon', ratingElement)[0];
-                               
-                               this._toggleIcon(icon, ~~rating <= ~~this._input.value);
-                       }.bind(this));
-               },
-               
-               /**
-                * Toggles the state of the given icon based on the given state parameter.
-                * 
-                * @param       {HTMLElement}   icon            toggled icon
-                * @param       {boolean}       active          is `true` if icon will be changed to `active` state, otherwise changed to `default` state
-                */
-               _toggleIcon: function(icon, active) {
-                       active = active || false;
-                       
-                       if (active) {
-                               for (var i = 0; i < this._defaultCssClasses.length; i++) {
-                                       icon.classList.remove(this._defaultCssClasses[i]);
-                               }
-                               
-                               for (var i = 0; i < this._activeCssClasses.length; i++) {
-                                       icon.classList.add(this._activeCssClasses[i]);
-                               }
-                       }
-                       else {
-                               for (var i = 0; i < this._activeCssClasses.length; i++) {
-                                       icon.classList.remove(this._activeCssClasses[i]);
-                               }
-                               
-                               for (var i = 0; i < this._defaultCssClasses.length; i++) {
-                                       icon.classList.add(this._defaultCssClasses[i]);
-                               }
-                       }
-               }
-       };
-       
-       return FormBuilderFieldRating;
-});
-
-/**
- * Abstract implementation of a form field dependency.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract',['./Manager'], function(DependencyManager) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function Abstract(dependentElementId, fieldId) {
-               this.init(dependentElementId, fieldId);
-       };
-       Abstract.prototype = {
-               /**
-                * Checks if the dependency is met.
-                * 
-                * @abstract
-                */
-               checkDependency: function() {
-                       throw new Error("Missing implementation of WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract.checkDependency!");
-               },
-               
-               /**
-                * Return the node whose availability depends on the value of a field.
-                * 
-                * @return      {HtmlElement}   dependent node
-                */
-               getDependentNode: function() {
-                       return this._dependentElement;
-               },
-               
-               /**
-                * Returns the field the availability of the element dependents on.
-                * 
-                * @return      {HtmlElement}   field controlling element availability
-                */
-               getField: function() {
-                       return this._field;
-               },
-               
-               /**
-                * Returns all fields requiring `change` event listeners for this
-                * dependency to be properly resolved.
-                * 
-                * @return      {HtmlElement[]}         fields to register event listeners on
-                */
-               getFields: function() {
-                       return this._fields;
-               },
-               
-               /**
-                * Initializes the new dependency object.
-                * 
-                * @param       {string}        dependentElementId      id of the (container of the) dependent element
-                * @param       {string}        fieldId                 id of the field controlling element availability
-                * 
-                * @throws      {Error}                                 if either depenent element id or field id are invalid
-                */
-               init: function(dependentElementId, fieldId) {
-                       this._dependentElement = elById(dependentElementId);
-                       if (this._dependentElement === null) {
-                               throw new Error("Unknown dependent element with container id '" + dependentElementId + "Container'.");
-                       }
-                       
-                       this._field = elById(fieldId);
-                       if (this._field === null) {
-                               this._fields = [];
-                               elBySelAll('input[type=radio][name=' + fieldId + ']', undefined, function(field) {
-                                       this._fields.push(field);
-                               }.bind(this));
-                               
-                               if (!this._fields.length) {
-                                       throw new Error("Unknown field with id '" + fieldId + "'.");
-                               }
-                       }
-                       else {
-                               this._fields = [this._field];
-                               
-                               // handle special case of boolean form fields that have to form fields
-                               if (this._field.tagName === 'INPUT' && this._field.type === 'radio' && elData(this._field, 'no-input-id') !== '') {
-                                       this._noField = elById(elData(this._field, 'no-input-id'));
-                                       if (this._noField === null) {
-                                               throw new Error("Cannot find 'no' input field for input field '" + fieldId + "'");
-                                       }
-                                       
-                                       this._fields.push(this._noField);
-                               }
-                       }
-                       
-                       DependencyManager.addDependency(this);
-               }
-       };
-       
-       return Abstract;
-});
-
-/**
- * Form field dependency implementation that requires the value of a field not to be empty.
- *
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Dependency/NonEmpty
- * @see        module:WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Dependency/NonEmpty',['./Abstract', 'Core'], function(Abstract, Core) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function NonEmpty(dependentElementId, fieldId) {
-               this.init(dependentElementId, fieldId);
-       };
-       Core.inherit(NonEmpty, Abstract, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract#checkDependency
-                */
-               checkDependency: function() {
-                       switch (this._field.tagName) {
-                               case 'INPUT':
-                                       switch (this._field.type) {
-                                               case 'checkbox':
-                                                       // TODO: check if working
-                                                       return this._field.checked;
-                                               
-                                               case 'radio':
-                                                       if (this._noField && this._noField.checked) {
-                                                               return false;
-                                                       }
-                                                       
-                                                       return this._field.checked;
-                                               
-                                               default:
-                                                       return this._field.value.trim().length !== 0;
-                                       }
-                               
-                               case 'SELECT':
-                                       // TODO: check if working for multiselect
-                                       return this._field.value.length !== 0;
-                               
-                               case 'TEXTAREA':
-                                       // TODO: check if working
-                                       return this._field.value.trim().length !== 0;
-                       }
-               }
-       });
-       
-       return NonEmpty;
-});
-
-/**
- * Form field dependency implementation that requires a field to have a certain value.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Dependency/Value
- * @see        module:WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Dependency/Value',['./Abstract', 'Core', './Manager'], function(Abstract, Core, Manager) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function Value(dependentElementId, fieldId, isNegated) {
-               this.init(dependentElementId, fieldId);
-               
-               this._isNegated = false;
-       };
-       Core.inherit(Value, Abstract, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract#checkDependency
-                */
-               checkDependency: function() {
-                       if (!this._values) {
-                               throw new Error("Values have not been set.");
-                       }
-                       
-                       var value;
-                       if (this._field) {
-                               if (Manager.isHiddenByDependencies(this._field)) {
-                                       return false;
-                               }
-                               
-                               value = this._field.value;
-                       }
-                       else {
-                               for (var i = 0, length = this._fields.length, field; i < length; i++) {
-                                       field = this._fields[i];
-                                       
-                                       if (field.checked) {
-                                               if (Manager.isHiddenByDependencies(field)) {
-                                                       return false;
-                                               }
-                                               
-                                               value = field.value;
-                                               
-                                               break;
-                                       }
-                               }
-                       }
-                       
-                       // do not use `Array.prototype.indexOf()` as we use a weak comparision
-                       for (var i = 0, length = this._values.length; i < length; i++) {
-                               if (this._values[i] == value) {
-                                       if (this._isNegated) {
-                                               return false;
-                                       }
-                                       
-                                       return true;
-                               }
-                       }
-                       
-                       if (this._isNegated) {
-                               return true;
-                       }
-                       
-                       return false;
-               },
-               
-               /**
-                * Sets if the field value may not have any of the set values.
-                * 
-                * @param       {bool}          negate
-                * @return      {WoltLabSuite/Core/Form/Builder/Field/Dependency/Value}
-                */
-               negate: function(negate) {
-                       this._isNegated = negate;
-                       
-                       return this;
-               },
-               
-               /**
-                * Sets the possible values the field may have for the dependency to be met.
-                * 
-                * @param       {array}         values
-                * @return      {WoltLabSuite/Core/Form/Builder/Field/Dependency/Value}
-                */
-               values: function(values) {
-                       this._values = values;
-                       
-                       return this;
-               }
-       });
-       
-       return Value;
-});
-
-/**
- * Data handler for a content language form builder field in an Ajax form.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Language/ContentLanguage
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Language/ContentLanguage',['Core', 'WoltLabSuite/Core/Language/Chooser', '../Value'], function(Core, LanguageChooser, FormBuilderFieldValue) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function FormBuilderFieldContentLanguage(fieldId) {
-               this.init(fieldId);
-       };
-       Core.inherit(FormBuilderFieldContentLanguage, FormBuilderFieldValue, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Field#destroy
-                */
-               destroy: function() {
-                       LanguageChooser.removeChooser(this._fieldId);
-               }
-       });
-       
-       return FormBuilderFieldContentLanguage;
-});
-
-/**
- * Abstract implementation of a handler for the visibility of container due the dependencies
- * of its children.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/Abstract
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/Abstract',['EventHandler', '../Manager'], function(EventHandler, DependencyManager) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function Abstract(containerId) {
-               this.init(containerId);
-       };
-       Abstract.prototype = {
-               /**
-                * Checks if the container should be visible and shows or hides it accordingly.
-                * 
-                * @abstract
-                */
-               checkContainer: function() {
-                       throw new Error("Missing implementation of WoltLabSuite/Core/Form/Builder/Field/Dependency/Container.checkContainer!");
-               },
-               
-               /**
-                * Initializes a new container dependency handler for the container with the given
-                * id.
-                * 
-                * @param       {string}        containerId     id of the handled container
-                * 
-                * @throws      {TypeError}                     if container id is no string
-                * @throws      {Error}                         if container id is invalid
-                */
-               init: function(containerId) {
-                       if (typeof containerId !== 'string') {
-                               throw new TypeError("Container id has to be a string.");
-                       }
-                       
-                       this._container = elById(containerId);
-                       if (this._container === null) {
-                               throw new Error("Unknown container with id '" + containerId + "'.");
-                       }
-                       
-                       DependencyManager.addContainerCheckCallback(this.checkContainer.bind(this));
-               }
-       };
-       
-       return Abstract
-});
-
-/**
- * Default implementation for a container visibility handler due to the dependencies of its
- * children that only considers the visibility of all of its children.
- *
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/Default
- * @see        module:WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/Default',['./Abstract', 'Core', '../Manager'], function(Abstract, Core, DependencyManager) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function Default(containerId) {
-               this.init(containerId);
-       };
-       Core.inherit(Default, Abstract, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/Default#checkContainer
-                */
-               checkContainer: function() {
-                       if (elDataBool(this._container, 'ignore-dependencies')) {
-                               return;
-                       }
-                       
-                       // only consider containers that have not been hidden by their own dependencies
-                       if (DependencyManager.isHiddenByDependencies(this._container)) {
-                               return;
-                       }
-                       
-                       var containerIsVisible = !elIsHidden(this._container);
-                       var containerShouldBeVisible = false;
-                       
-                       var children = this._container.children;
-                       var start = 0;
-                       // ignore container header for visibility considerations
-                       if (this._container.children.item(0).tagName === 'H2' || this._container.children.item(0).tagName === 'HEADER') {
-                               var start = 1;
-                       }
-                       
-                       for (var i = start, length = children.length; i < length; i++) {
-                               if (!elIsHidden(children.item(i))) {
-                                       containerShouldBeVisible = true;
-                                       break;
-                               }
-                       }
-                       
-                       if (containerIsVisible !== containerShouldBeVisible) {
-                               if (containerShouldBeVisible) {
-                                       elShow(this._container);
-                               }
-                               else {
-                                       elHide(this._container);
-                               }
-                               
-                               // check containers again to make sure parent containers can react to
-                               // changing the visibility of this container
-                               DependencyManager.checkContainers();
-                       }
-               }
-       });
-       
-       return Default;
-});
-
-/**
- * Container visibility handler implementation for a tab menu tab that, in addition to the
- * tab itself, also handles the visibility of the tab menu list item.
- *
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/Tab
- * @see        module:WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/Tab',['./Abstract', 'Core', 'Dom/Util', '../Manager', 'Ui/TabMenu'], function(Abstract, Core, DomUtil, DependencyManager, UiTabMenu) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function Tab(containerId) {
-               this.init(containerId);
-       };
-       Core.inherit(Tab, Abstract, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/Default#checkContainer
-                */
-               checkContainer: function() {
-                       // only consider containers that have not been hidden by their own dependencies
-                       if (DependencyManager.isHiddenByDependencies(this._container)) {
-                               return;
-                       }
-                       
-                       var containerIsVisible = !elIsHidden(this._container);
-                       var containerShouldBeVisible = false;
-                       
-                       var children = this._container.children;
-                       for (var i = 0, length = children.length; i < length; i++) {
-                               if (!elIsHidden(children.item(i))) {
-                                       containerShouldBeVisible = true;
-                                       break;
-                               }
-                       }
-                       
-                       if (containerIsVisible !== containerShouldBeVisible) {
-                               var tabMenuListItem = elBySel('#' + DomUtil.identify(this._container.parentNode) + ' > nav > ul > li[data-name=' + this._container.id + ']', this._container.parentNode.parentNode);
-                               if (tabMenuListItem === null) {
-                                       throw new Error("Cannot find tab menu entry for tab '" + this._container.id + "'.");
-                               }
-                               
-                               if (containerShouldBeVisible) {
-                                       elShow(this._container);
-                                       elShow(tabMenuListItem);
-                               }
-                               else {
-                                       elHide(this._container);
-                                       elHide(tabMenuListItem);
-                                       
-                                       var tabMenu = UiTabMenu.getTabMenu(DomUtil.identify(tabMenuListItem.closest('.tabMenuContainer')));
-                                       
-                                       // check if currently active tab will be hidden
-                                       if (tabMenu.getActiveTab() === tabMenuListItem) {
-                                               tabMenu.selectFirstVisible();
-                                       }
-                               }
-                               
-                               // check containers again to make sure parent containers can react to
-                               // changing the visibility of this container
-                               DependencyManager.checkContainers();
-                       }
-               }
-       });
-       
-       return Tab;
-});
-
-/**
- * Container visibility handler implementation for a tab menu that checks visibility
- * based on the visibility of its tab menu list items.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/TabMenu
- * @see        module:WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/TabMenu',['./Abstract', 'Core', 'Dom/Util', '../Manager', 'Ui/TabMenu'], function(Abstract, Core, DomUtil, DependencyManager, UiTabMenu) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function TabMenu(containerId) {
-               this.init(containerId);
-       };
-       Core.inherit(TabMenu, Abstract, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/Default#checkContainer
-                */
-               checkContainer: function() {
-                       // only consider containers that have not been hidden by their own dependencies
-                       if (DependencyManager.isHiddenByDependencies(this._container)) {
-                               return;
-                       }
-                       
-                       var containerIsVisible = !elIsHidden(this._container);
-                       var containerShouldBeVisible = false;
-                       
-                       var tabMenuListItems = elBySelAll('#' + DomUtil.identify(this._container) + ' > nav > ul > li', this._container.parentNode);
-                       for (var i = 0, length = tabMenuListItems.length; i < length; i++) {
-                               if (!elIsHidden(tabMenuListItems[i])) {
-                                       containerShouldBeVisible = true;
-                                       break;
-                               }
-                       }
-                       
-                       if (containerIsVisible !== containerShouldBeVisible) {
-                               if (containerShouldBeVisible) {
-                                       elShow(this._container);
-                                       
-                                       UiTabMenu.getTabMenu(DomUtil.identify(this._container)).selectFirstVisible();
-                               }
-                               else {
-                                       elHide(this._container);
-                               }
-                               
-                               // check containers again to make sure parent containers can react to
-                               // changing the visibility of this container
-                               DependencyManager.checkContainers();
-                       }
-               }
-       });
-       
-       return TabMenu;
-});
-
-/**
- * Abstract implementation of the JavaScript component of a form field handling
- * a list of packages.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList',['Dom/ChangeListener', 'Dom/Traverse', 'Dom/Util', 'EventKey', 'Language'], function(DomChangeListener, DomTraverse, DomUtil, EventKey, Language) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function AbstractPackageList(formFieldId, existingPackages) {
-               this.init(formFieldId, existingPackages);
-       };
-       AbstractPackageList.prototype = {
-               /**
-                * Initializes the package list handler.
-                * 
-                * @param       {string}        formFieldId             id of the associated form field
-                * @param       {object[]}      existingPackages        data of existing packages
-                */
-               init: function(formFieldId, existingPackages) {
-                       this._formFieldId = formFieldId;
-                       
-                       this._packageList = elById(this._formFieldId + '_packageList');
-                       if (this._packageList === null) {
-                               throw new Error("Cannot find package list for packages field with id '" + this._formFieldId + "'.");
-                       }
-                       
-                       this._packageIdentifier = elById(this._formFieldId + '_packageIdentifier');
-                       if (this._packageIdentifier === null) {
-                               throw new Error("Cannot find package identifier form field for packages field with id '" + this._formFieldId + "'.");
-                       }
-                       this._packageIdentifier.addEventListener('keypress', this._keyPress.bind(this));
-                       
-                       this._addButton = elById(this._formFieldId + '_addButton');
-                       if (this._addButton === null) {
-                               throw new Error("Cannot find add button for packages field with id '" + this._formFieldId + "'.");
-                       }
-                       this._addButton.addEventListener('click', this._addPackage.bind(this));
-                       
-                       this._form = this._packageList.closest('form');
-                       if (this._form === null) {
-                               throw new Error("Cannot find form element for packages field with id '" + this._formFieldId + "'.");
-                       }
-                       this._form.addEventListener('submit', this._submit.bind(this));
-                       
-                       existingPackages.forEach(this._addPackageByData.bind(this));
-               },
-               
-               /**
-                * Adds a package to the package list as a consequence of the given
-                * event. If the package data is invalid, an error message is shown
-                * and no package is added.
-                * 
-                * @param       {Event}         event   event that triggered trying to add the package
-                */
-               _addPackage: function(event) {
-                       event.preventDefault();
-                       event.stopPropagation();
-                       
-                       // validate data
-                       if (!this._validateInput()) {
-                               return;
-                       }
-                       
-                       this._addPackageByData(this._getInputData());
-                       
-                       // empty fields
-                       this._emptyInput();
-                       
-                       this._packageIdentifier.focus();
-               },
-               
-               /**
-                * Adds a package to the package list using the given package data.
-                * 
-                * @param       {object}        packageData
-                */
-               _addPackageByData: function(packageData) {
-                       // add package to list
-                       var listItem = elCreate('li');
-                       this._populateListItem(listItem, packageData);
-                       
-                       // add delete button
-                       var deleteButton = elCreate('span');
-                       deleteButton.className = 'icon icon16 fa-times pointer jsTooltip';
-                       elAttr(deleteButton, 'title', Language.get('wcf.global.button.delete'));
-                       deleteButton.addEventListener('click', this._removePackage.bind(this));
-                       DomUtil.prepend(deleteButton, listItem);
-                       
-                       this._packageList.appendChild(listItem);
-                       
-                       DomChangeListener.trigger();
-               },
-               
-               /**
-                * Creates the hidden fields when the form is submitted.
-                * 
-                * @param       {HTMLElement}   listElement     package list element from the package list
-                * @param       {int}           index           package index
-                */
-               _createSubmitFields: function(listElement, index) {
-                       var packageIdentifier = elCreate('input');
-                       elAttr(packageIdentifier, 'type', 'hidden');
-                       elAttr(packageIdentifier, 'name', this._formFieldId + '[' + index + '][packageIdentifier]')
-                       packageIdentifier.value = elData(listElement, 'package-identifier');
-                       this._form.appendChild(packageIdentifier);
-               },
-               
-               /**
-                * Empties the input fields.
-                */
-               _emptyInput() {
-                       this._packageIdentifier.value = '';
-               },
-               
-               /**
-                * Returns the error element for the given form field element.
-                * If `createIfNonExistent` is not given or `false`, `null` is returned
-                * if there is no error element, otherwise an empty error element
-                * is created and returned.
-                * 
-                * @param       {?boolean}      createIfNonExistent
-                * @return      {?HTMLElement}
-                */
-               _getErrorElement: function(element, createIfNoNExistent) {
-                       var error = DomTraverse.nextByClass(element, 'innerError');
-                       
-                       if (error === null && createIfNoNExistent) {
-                               error = elCreate('small');
-                               error.className = 'innerError';
-                               
-                               DomUtil.insertAfter(error, element);
-                       }
-                       
-                       return error;
-               },
-               
-               /**
-                * Returns the current data of the input fields to add a new package. 
-                * 
-                * @return      {object}
-                */
-               _getInputData: function() {
-                       return {
-                               packageIdentifier: this._packageIdentifier.value
-                       };
-               },
-               
-               /**
-                * Returns the error element for the package identifier form field.
-                * If `createIfNonExistent` is not given or `false`, `null` is returned
-                * if there is no error element, otherwise an empty error element
-                * is created and returned.
-                * 
-                * @param       {?boolean}      createIfNonExistent
-                * @return      {?HTMLElement}
-                */
-               _getPackageIdentifierErrorElement: function(createIfNonExistent) {
-                       return this._getErrorElement(this._packageIdentifier, createIfNonExistent);
-               },
-               
-               /**
-                * Adds a package to the package list after pressing ENTER in a
-                * text field.
-                * 
-                * @param       {Event}         event
-                */
-               _keyPress: function(event) {
-                       if (EventKey.Enter(event)) {
-                               this._addPackage(event);
-                       }
-               },
-               
-               /**
-                * Adds all necessary package-relavant data to the given list item.
-                * 
-                * @param       {HTMLElement}   listItem        package list element holding package data
-                * @param       {object}        packageData     package data
-                */
-               _populateListItem(listItem, packageData) {
-                       elData(listItem, 'package-identifier', packageData.packageIdentifier);
-               },
-               
-               /**
-                * Removes a package by clicking on its delete button.
-                * 
-                * @param       {Event}         event           delete button click event
-                */
-               _removePackage: function(event) {
-                       elRemove(event.currentTarget.closest('li'));
-                       
-                       // remove field errors if the last package has been deleted
-                       if (
-                               !this._packageList.childElementCount &&
-                               this._packageList.nextElementSibling.tagName === 'SMALL' &&
-                               this._packageList.nextElementSibling.classList.contains('innerError')
-                       ) {
-                               elRemove(this._packageList.nextElementSibling);
-                       }
-               },
-               
-               /**
-                * Adds all necessary (hidden) form fields to the form when
-                * submitting the form.
-                */
-               _submit: function() {
-                       DomTraverse.childrenByTag(this._packageList, 'LI').forEach(this._createSubmitFields.bind(this));
-               },
-               
-               /**
-                * Returns `true` if the currently entered package data is valid.
-                * Otherwise `false` is returned and relevant error messages are
-                * shown.
-                * 
-                * @return      {boolean}
-                */
-               _validateInput: function() {
-                       return this._validatePackageIdentifier();
-               },
-               
-               /**
-                * Returns `true` if the currently entered package identifier is
-                * valid. Otherwise `false` is returned and an error message is
-                * shown.
-                * 
-                * @return      {boolean}
-                */
-               _validatePackageIdentifier: function() {
-                       var packageIdentifier = this._packageIdentifier.value;
-                       
-                       if (packageIdentifier === '') {
-                               this._getPackageIdentifierErrorElement(true).textContent = Language.get('wcf.global.form.error.empty');
-                               
-                               return false;
-                       }
-                       
-                       if (packageIdentifier.length < 3) {
-                               this._getPackageIdentifierErrorElement(true).textContent = Language.get('wcf.acp.devtools.project.packageIdentifier.error.minimumLength');
-                               
-                               return false;
-                       }
-                       else if (packageIdentifier.length > 191) {
-                               this._getPackageIdentifierErrorElement(true).textContent = Language.get('wcf.acp.devtools.project.packageIdentifier.error.maximumLength');
-                               
-                               return false;
-                       }
-                       
-                       // see `wcf\data\package\Package::isValidPackageName()`
-                       if (!packageIdentifier.match(/^[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/)) {
-                               this._getPackageIdentifierErrorElement(true).textContent = Language.get('wcf.acp.devtools.project.packageIdentifier.error.format');
-                               
-                               return false;
-                       }
-                       
-                       // check if package has already been added
-                       var duplicate = false;
-                       DomTraverse.childrenByTag(this._packageList, 'LI').forEach(function(listItem, index) {
-                               if (elData(listItem, 'package-identifier') === packageIdentifier) {
-                                       duplicate = true;
-                               }
-                       });
-                       
-                       if (duplicate) {
-                               this._getPackageIdentifierErrorElement(true).textContent = Language.get('wcf.acp.devtools.project.packageIdentifier.error.duplicate');
-                               
-                               return false;
-                       }
-                       
-                       // remove outdated errors
-                       var error = this._getPackageIdentifierErrorElement();
-                       if (error !== null) {
-                               elRemove(error);
-                       }
-                       
-                       return true;
-               },
-               
-               /**
-                * Returns `true` if the given version is valid. Otherwise `false`
-                * is returned and an error message is shown.
-                * 
-                * @param       {string}        version                 validated version
-                * @param       {function}      versionErrorGetter      returns the version error element
-                * @return      {boolean}
-                */
-               _validateVersion: function(version, versionErrorGetter) {
-                       // see `wcf\data\package\Package::isValidVersion()`
-                       // the version is no a required attribute
-                       if (version !== '') {
-                               if (version.length > 255) {
-                                       versionErrorGetter(true).textContent = Language.get('wcf.acp.devtools.project.packageVersion.error.maximumLength');
-                                       
-                                       return false;
-                               }
-                               
-                               // see `wcf\data\package\Package::isValidVersion()`
-                               if (!version.match(/^([0-9]+)\.([0-9]+)\.([0-9]+)(\ (a|alpha|b|beta|d|dev|rc|pl)\ ([0-9]+))?$/i)) {
-                                       versionErrorGetter(true).textContent = Language.get('wcf.acp.devtools.project.packageVersion.error.format');
-                                       
-                                       return false;
-                               }
-                       }
-                       
-                       // remove outdated errors
-                       var error = versionErrorGetter();
-                       if (error !== null) {
-                               elRemove(error);
-                       }
-                       
-                       return true;
-               }
-       };
-       
-       return AbstractPackageList;
-});
-
-/**
- * Manages the packages entered in a devtools project excluded package form field.
- *
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/ExcludedPackages
- * @see        module:WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/ExcludedPackages',['./AbstractPackageList', 'Core', 'Language'], function(AbstractPackageList, Core, Language) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function ExcludedPackages(formFieldId, existingPackages) {
-               this.init(formFieldId, existingPackages);
-       };
-       Core.inherit(ExcludedPackages, AbstractPackageList, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList#init
-                */
-               init: function(formFieldId, existingPackages) {
-                       ExcludedPackages._super.prototype.init.call(this, formFieldId, existingPackages);
-                       
-                       this._version = elById(this._formFieldId + '_version');
-                       if (this._version === null) {
-                               throw new Error("Cannot find version form field for packages field with id '" + this._formFieldId + "'.");
-                       }
-                       this._version.addEventListener('keypress', this._keyPress.bind(this));
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList#_createSubmitFields
-                */
-               _createSubmitFields: function(listElement, index) {
-                       ExcludedPackages._super.prototype._createSubmitFields.call(this, listElement, index);
-                       
-                       var version = elCreate('input');
-                       elAttr(version, 'type', 'hidden');
-                       elAttr(version, 'name', this._formFieldId + '[' + index + '][version]')
-                       version.value = elData(listElement, 'version');
-                       this._form.appendChild(version);
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList#_emptyInput
-                */
-               _emptyInput() {
-                       ExcludedPackages._super.prototype._emptyInput.call(this);
-                       
-                       this._version.value = '';
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList#_getInputData
-                */
-               _getInputData: function() {
-                       return Core.extend(ExcludedPackages._super.prototype._getInputData.call(this), {
-                               version: this._version.value
-                       });
-               },
-               
-               /**
-                * Returns the error element for the version form field.
-                * If `createIfNonExistent` is not given or `false`, `null` is returned
-                * if there is no error element, otherwise an empty error element
-                * is created and returned.
-                *
-                * @param       {?boolean}      createIfNonExistent
-                * @return      {?HTMLElement}
-                */
-               _getVersionErrorElement: function(createIfNonExistent) {
-                       return this._getErrorElement(this._version, createIfNonExistent);
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList#_populateListItem
-                */
-               _populateListItem(listItem, packageData) {
-                       ExcludedPackages._super.prototype._populateListItem.call(this, listItem, packageData);
-                       
-                       elData(listItem, 'version', packageData.version);
-                       listItem.innerHTML = ' ' + Language.get('wcf.acp.devtools.project.excludedPackage.excludedPackage', {
-                               packageIdentifier: packageData.packageIdentifier,
-                               version: packageData.version
-                       });
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList#_validateInput
-                */
-               _validateInput: function() {
-                       return ExcludedPackages._super.prototype._validateInput.call(this) && this._validateVersion(
-                               this._version.value,
-                               this._getVersionErrorElement.bind(this)
-                       );
-               }
-       });
-       
-       return ExcludedPackages;
-});
-
-/**
- * Manages the instructions entered in a devtools project instructions form field. 
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/Instructions
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/Instructions',[
-       'Dom/ChangeListener',
-       'Dom/Traverse',
-       'Dom/Util',
-       'EventKey',
-       'Language',
-       'Ui/Confirmation',
-       'Ui/Dialog',
-       'WoltLabSuite/Core/Ui/Sortable/List'
-], function(
-       DomChangeListener,
-       DomTraverse,
-       DomUtil,
-       EventKey,
-       Language,
-       UiConfirmation,
-       UiDialog,
-       UiSortableList
-) {
-       "use strict";
-       
-       var _applicationPips = ['acpTemplate', 'file', 'script', 'template'];
-       
-       /**
-        * @constructor
-        */
-       function Instructions(
-               formFieldId,
-               instructionsTemplate,
-               instructionsEditDialogTemplate,
-               instructionEditDialogTemplate,
-               pipDefaultFilenames,
-               existingInstructions
-       ) {
-               this.init(
-                       formFieldId,
-                       instructionsTemplate,
-                       instructionsEditDialogTemplate,
-                       instructionEditDialogTemplate,
-                       pipDefaultFilenames,
-                       existingInstructions || []
-               );
-       };
-       Instructions.prototype = {
-               /**
-                * Initializes the instructions handler.
-                * 
-                * @param       {string}        formFieldId                     id of the associated form field
-                * @param       {Template}      instructionsTemplate            template used for a new set of instructions
-                * @param       {Template}      instructionsEditDialogTemplate  template used for instructions edit dialogs
-                * @param       {Template}      instructionEditDialogTemplate   template used for instruction edit dialogs
-                * @param       {object}        pipDefaultFilenames             maps pip names to their default filenames
-                * @param       {object[]}      existingInstructions            data of existing instructions
-                */
-               init: function(
-                       formFieldId,
-                       instructionsTemplate,
-                       instructionsEditDialogTemplate,
-                       instructionEditDialogTemplate,
-                       pipDefaultFilenames,
-                       existingInstructions
-               ) {
-                       this._formFieldId = formFieldId;
-                       this._instructionsTemplate = instructionsTemplate;
-                       this._instructionsEditDialogTemplate = instructionsEditDialogTemplate;
-                       this._instructionEditDialogTemplate = instructionEditDialogTemplate;
-                       this._instructionsCounter = 0;
-                       this._pipDefaultFilenames = pipDefaultFilenames;
-                       this._instructionCounter = 0;
-                       
-                       this._instructionsList = elById(this._formFieldId + '_instructionsList');
-                       if (this._instructionsList === null) {
-                               throw new Error("Cannot find package list for packages field with id '" + this._formFieldId + "'.");
-                       }
-                       
-                       this._instructionsType = elById(this._formFieldId + '_instructionsType');
-                       if (this._instructionsType === null) {
-                               throw new Error("Cannot find instruction type form field for instructions field with id '" + this._formFieldId + "'.");
-                       }
-                       this._instructionsType.addEventListener('change', this._toggleFromVersionFormField.bind(this));
-                       
-                       this._fromVersion = elById(this._formFieldId + '_fromVersion');
-                       if (this._fromVersion === null) {
-                               throw new Error("Cannot find from version form field for instructions field with id '" + this._formFieldId + "'.");
-                       }
-                       this._fromVersion.addEventListener('keypress', this._instructionsKeyPress.bind(this));
-                       
-                       this._addButton = elById(this._formFieldId + '_addButton');
-                       if (this._addButton === null) {
-                               throw new Error("Cannot find add button for instructions field with id '" + this._formFieldId + "'.");
-                       }
-                       this._addButton.addEventListener('click', this._addInstructions.bind(this));
-                       
-                       this._form = this._instructionsList.closest('form');
-                       if (this._form === null) {
-                               throw new Error("Cannot find form element for instructions field with id '" + this._formFieldId + "'.");
-                       }
-                       this._form.addEventListener('submit', this._submit.bind(this));
-                       
-                       var hasInstallInstructions = false;
-                       
-                       for (var index in existingInstructions) {
-                               var instructions = existingInstructions[index];
-                               
-                               if (instructions.type === 'install') {
-                                       hasInstallInstructions = true;
-                                       break;
-                               }
-                       }
-                       
-                       // ensure that there are always installation instructions
-                       if (!hasInstallInstructions) {
-                               this._addInstructionsByData({
-                                       fromVersion: '',
-                                       type: 'install'
-                               });
-                       }
-                       
-                       existingInstructions.forEach(this._addInstructionsByData.bind(this));
-                       
-                       DomChangeListener.trigger();
-               },
-               
-               /**
-                * Adds an instruction to a set of instructions as a consequence
-                * of the given event. If the instruction data is invalid, an
-                * error message is shown and no instruction is added.
-                * 
-                * @param       {Event}         event   event that triggered trying to add the instruction
-                */
-               _addInstruction: function(event) {
-                       event.preventDefault();
-                       event.stopPropagation();
-                       
-                       var instructionsId = elData(event.currentTarget.closest('li.section'), 'instructions-id');
-                       
-                       // note: data will be validated/filtered by the server
-                       
-                       var pipField = elById(this._formFieldId + '_instructions' + instructionsId + '_pip');
-                       
-                       // ignore pressing button if no PIP has been selected
-                       if (!pipField.value) {
-                               return;
-                       }
-                       
-                       var valueField = elById(this._formFieldId + '_instructions' + instructionsId + '_value');
-                       var runStandaloneField = elById(this._formFieldId + '_instructions' + instructionsId + '_runStandalone');
-                       var applicationField = elById(this._formFieldId + '_instructions' + instructionsId + '_application');
-                       
-                       this._addInstructionByData(instructionsId, {
-                               application: _applicationPips.indexOf(pipField.value) !== -1 ? applicationField.value : '',
-                               pip: pipField.value,
-                               runStandalone: ~~runStandaloneField.checked,
-                               value: valueField.value
-                       });
-                       
-                       // empty fields
-                       pipField.value = '';
-                       valueField.value = '';
-                       runStandaloneField.checked = false;
-                       applicationField.value = '';
-                       elById(this._formFieldId + '_instructions' + instructionsId + '_valueDescription').innerHTML = Language.get('wcf.acp.devtools.project.instruction.value.description');
-                       this._toggleApplicationFormField(instructionsId);
-                       
-                       DomChangeListener.trigger();
-               },
-               
-               /**
-                * Adds an instruction to the set of instructions with the given id.
-                * 
-                * @param       {int}           instructionsId
-                * @param       {object}        instructionData
-                */
-               _addInstructionByData: function(instructionsId, instructionData) {
-                       var instructionId = ++this._instructionCounter;
-                       
-                       var instructionList = elById(this._formFieldId + '_instructions' + instructionsId + '_instructionList');
-                       
-                       var listItem = elCreate('li');
-                       listItem.className = 'sortableNode';
-                       listItem.id = this._formFieldId + '_instruction' + instructionId;
-                       elData(listItem, 'instruction-id', instructionId);
-                       elData(listItem, 'application', instructionData.application);
-                       elData(listItem, 'pip', instructionData.pip);
-                       elData(listItem, 'runStandalone', instructionData.runStandalone);
-                       elData(listItem, 'value', instructionData.value);
-                       
-                       var content = '' +
-                               '<div class="sortableNodeLabel">' +
-                               '       <div class="jsDevtoolsProjectInstruction">' +
-                               '               ' + Language.get('wcf.acp.devtools.project.instruction.instruction', instructionData);
-                       
-                       if (instructionData.errors) {
-                               for (var index in instructionData.errors) {
-                                       content += '<small class="innerError">' + instructionData.errors[index] + '</small>';
-                               }
-                       }
-                       
-                       content += '' +
-                                       '       </div>' +
-                               '       <span class="statusDisplay sortableButtonContainer">' +
-                               '               <span class="icon icon16 fa-pencil pointer jsTooltip" id="' + this._formFieldId + '_instruction' + instructionId + '_editButton" title="' + Language.get('wcf.global.button.edit') + '"></span>' +
-                               '               <span class="icon icon16 fa-times pointer jsTooltip" id="' + this._formFieldId + '_instruction' + instructionId + '_deleteButton" title="' + Language.get('wcf.global.button.delete') + '"></span>' +
-                               '       </span>' +
-                               '</div>';
-                       
-                       listItem.innerHTML = content;
-                       
-                       instructionList.appendChild(listItem);
-                       
-                       elById(this._formFieldId + '_instruction' + instructionId + '_deleteButton').addEventListener('click', this._removeInstruction.bind(this));
-                       elById(this._formFieldId + '_instruction' + instructionId + '_editButton').addEventListener('click', this._editInstruction.bind(this));
-               },
-               
-               /**
-                * Adds a set of instructions as a consequenc of the given event.
-                * If the instructions data is invalid, an error message is shown
-                * and no instruction set is added.
-                * 
-                * @param       {Event}         event   event that triggered trying to add the instructions
-                */
-               _addInstructions: function(event) {
-                       event.preventDefault();
-                       event.stopPropagation();
-                       
-                       // validate data
-                       if (!this._validateInstructionsType() || (this._instructionsType.value === 'update' && !this._validateFromVersion(this._fromVersion))) {
-                               return;
-                       }
-                       
-                       this._addInstructionsByData({
-                               fromVersion: this._instructionsType.value === 'update' ? this._fromVersion.value : '',
-                               type: this._instructionsType.value
-                       });
-                       
-                       // empty fields
-                       this._instructionsType.value = '';
-                       this._fromVersion.value = '';
-                       
-                       this._toggleFromVersionFormField();
-                       
-                       DomChangeListener.trigger();
-               },
-               
-               /**
-                * Adds a set of instructions.
-                * 
-                * @param       {object}        instructionData
-                */
-               _addInstructionsByData: function(instructionsData) {
-                       var instructionsId = ++this._instructionsCounter;
-                       
-                       var listItem = elCreate('li');
-                       listItem.className = 'section';
-                       listItem.innerHTML = this._instructionsTemplate.fetch({
-                               instructionsId: instructionsId,
-                               sectionTitle: Language.get('wcf.acp.devtools.project.instructions.type.' + instructionsData.type + '.title', {
-                                       fromVersion: instructionsData.fromVersion
-                               }),
-                               type: instructionsData.type
-                       });
-                       
-                       listItem.id = this._formFieldId + '_instructions' + instructionsId;
-                       elData(listItem, 'instructions-id', instructionsId);
-                       elData(listItem, 'type', instructionsData.type);
-                       elData(listItem, 'fromVersion', instructionsData.fromVersion);
-                       
-                       elById(this._formFieldId + '_instructions' + instructionsId + '_valueDescription')
-                       
-                       this._instructionsList.appendChild(listItem);
-                       
-                       var instructionListContainer = elById(this._formFieldId + '_instructions' + instructionsId + '_instructionListContainer');
-                       for (var errorMessage of instructionsData.errors || []) {
-                               var small = elCreate('small');
-                               small.className = 'innerError';
-                               small.innerHTML = errorMessage;
-                               
-                               instructionListContainer.parentNode.insertBefore(small, instructionListContainer);
-                       }
-                       
-                       new UiSortableList({
-                               containerId: instructionListContainer.id,
-                               isSimpleSorting: true,
-                               options: {
-                                       toleranceElement: '> div'
-                               }
-                       });
-                       
-                       var deleteButton = elById(this._formFieldId + '_instructions' + instructionsId + '_deleteButton');
-                       if (instructionsData.type === 'update') {
-                               elById(this._formFieldId + '_instructions' + instructionsId + '_deleteButton').addEventListener('click', this._removeInstructions.bind(this));
-                               elById(this._formFieldId + '_instructions' + instructionsId + '_editButton').addEventListener('click', this._editInstructions.bind(this));
-                       }
-                       
-                       elById(this._formFieldId + '_instructions' + instructionsId + '_pip').addEventListener('change', this._changeInstructionPip.bind(this));
-                       elById(this._formFieldId + '_instructions' + instructionsId + '_value').addEventListener('keypress', this._instructionKeyPress.bind(this));
-                       elById(this._formFieldId + '_instructions' + instructionsId + '_addButton').addEventListener('click', this._addInstruction.bind(this));
-                       
-                       if (instructionsData.instructions) {
-                               for (var index in instructionsData.instructions) {
-                                       this._addInstructionByData(instructionsId, instructionsData.instructions[index]);
-                               }
-                       }
-               },
-               
-               /**
-                * Is called if the selected package installation plugin of an
-                * instruction is changed.
-                * 
-                * @param       {Event}         event           change event
-                */
-               _changeInstructionPip: function(event) {
-                       var pip = event.currentTarget.value;
-                       var instructionsId = elData(event.currentTarget.closest('li.section'), 'instructions-id');
-                       var description = elById(this._formFieldId + '_instructions' + instructionsId + '_valueDescription');
-                       
-                       // update value description
-                       if (this._pipDefaultFilenames[pip] !== '') {
-                               description.innerHTML = Language.get('wcf.acp.devtools.project.instruction.value.description.defaultFilename', {
-                                       defaultFilename: this._pipDefaultFilenames[pip]
-                               });
-                       }
-                       else {
-                               description.innerHTML = Language.get('wcf.acp.devtools.project.instruction.value.description');
-                       }
-                       
-                       var valueDlClassList = elById(this._formFieldId + '_instructions' + instructionsId + '_value').closest('dl').classList;
-                       var applicationDl = elById(this._formFieldId + '_instructions' + instructionsId + '_application').closest('dl');
-                       
-                       // toggle application selector
-                       this._toggleApplicationFormField(instructionsId);
-               },
-               
-               /**
-                * Opens a dialog to edit an existing instruction.
-                * 
-                * @param       {Event}         event   edit button click event
-                */
-               _editInstruction: function(event) {
-                       var listItem = event.currentTarget.closest('li');
-                       
-                       var instructionId = elData(listItem, 'instruction-id');
-                       var application = elData(listItem, 'application');
-                       var pip = elData(listItem, 'pip');
-                       var runStandalone = elDataBool(listItem, 'runStandalone');
-                       var value = elData(listItem, 'value');
-                       
-                       var dialogContent = this._instructionEditDialogTemplate.fetch({
-                               runStandalone: runStandalone,
-                               value: value
-                       });
-                       
-                       var dialogId = 'instructionEditDialog' + instructionId;
-                       if (!UiDialog.getDialog(dialogId)) {
-                               UiDialog.openStatic(dialogId, dialogContent, {
-                                       onSetup: function(content) {
-                                               var applicationSelect = elBySel('select[name=application]', content);
-                                               var pipSelect = elBySel('select[name=pip]', content);
-                                               var runStandaloneInput = elBySel('input[name=runStandalone]', content);
-                                               var valueInput = elBySel('input[name=value]', content);
-                                               
-                                               // set values of `select` elements
-                                               applicationSelect.value = application;
-                                               pipSelect.value = pip;
-                                               
-                                               var submit = function() {
-                                                       var listItem = elById(this._formFieldId + '_instruction' + instructionId);
-                                                       elData(listItem, 'application', _applicationPips.indexOf(pipSelect.value) !== -1 ? applicationSelect.value : '');
-                                                       elData(listItem, 'pip', pipSelect.value);
-                                                       elData(listItem, 'runStandalone', ~~runStandaloneInput.checked);
-                                                       elData(listItem, 'value', valueInput.value);
-                                                       
-                                                       // note: data will be validated/filtered by the server
-                                                       
-                                                       elByClass('jsDevtoolsProjectInstruction', listItem)[0].innerHTML = Language.get('wcf.acp.devtools.project.instruction.instruction', {
-                                                               application: elData(listItem, 'application'),
-                                                               pip: elData(listItem, 'pip'),
-                                                               runStandalone: elDataBool(listItem, 'runStandalone'),
-                                                               value: elData(listItem, 'value'),
-                                                       });
-                                                       
-                                                       DomChangeListener.trigger();
-                                                       
-                                                       UiDialog.close(dialogId);
-                                               }.bind(this);
-                                               
-                                               valueInput.addEventListener('keypress', function(event) {
-                                                       if (EventKey.Enter(event)) {
-                                                               submit();
-                                                       }
-                                               });
-                                               
-                                               elBySel('button[data-type=submit]', content).addEventListener('click', submit);
-                                               
-                                               var pipChange = function() {
-                                                       var pip = pipSelect.value;
-                                                       
-                                                       if (_applicationPips.indexOf(pip) !== -1) {
-                                                               elShow(applicationSelect.closest('dl'));
-                                                       }
-                                                       else {
-                                                               elHide(applicationSelect.closest('dl'));
-                                                       }
-                                                       
-                                                       var description = DomTraverse.nextByTag(valueInput, 'SMALL');
-                                                       if (this._pipDefaultFilenames[pip] !== '') {
-                                                               description.innerHTML = Language.get('wcf.acp.devtools.project.instruction.value.description.defaultFilename', {
-                                                                       defaultFilename: this._pipDefaultFilenames[pip]
-                                                               });
-                                                       }
-                                                       else {
-                                                               description.innerHTML = Language.get('wcf.acp.devtools.project.instruction.value.description');
-                                                       }
-                                               }.bind(this);
-                                               
-                                               pipSelect.addEventListener('change', pipChange);
-                                               pipChange();
-                                       }.bind(this),
-                                       title: Language.get('wcf.acp.devtools.project.instruction.edit')
-                               });
-                       }
-                       else {
-                               UiDialog.openStatic(dialogId);
-                       }
-               },
-               
-               /**
-                * Opens a dialog to edit an existing set of instructions.
-                * 
-                * @param       {Event}         event   edit button click event
-                */
-               _editInstructions: function(event) {
-                       var listItem = event.currentTarget.closest('li');
-                       
-                       var instructionsId = elData(listItem, 'instructions-id');
-                       var fromVersion = elData(listItem, 'fromVersion');
-                       
-                       var dialogContent = this._instructionsEditDialogTemplate.fetch({
-                               fromVersion: fromVersion
-                       });
-                       
-                       var dialogId = 'instructionsEditDialog' + instructionsId;
-                       if (!UiDialog.getDialog(dialogId)) {
-                               UiDialog.openStatic(dialogId, dialogContent, {
-                                       onSetup: function (content) {
-                                               var fromVersion = elBySel('input[name=fromVersion]', content);
-                                               
-                                               var submit = function () {
-                                                       if (!this._validateFromVersion(fromVersion)) {
-                                                               return;
-                                                       }
-                                                       
-                                                       var instructions = elById(this._formFieldId + '_instructions' + instructionsId);
-                                                       elData(instructions, 'fromVersion', fromVersion.value);
-                                                       
-                                                       elByClass('jsInstructionsTitle', instructions)[0].textContent = Language.get('wcf.acp.devtools.project.instructions.type.update.title', {
-                                                               fromVersion: fromVersion.value
-                                                       });
-                                                       
-                                                       DomChangeListener.trigger();
-                                                       
-                                                       UiDialog.close(dialogId);
-                                               }.bind(this);
-                                               
-                                               fromVersion.addEventListener('keypress', function (event) {
-                                                       if (EventKey.Enter(event)) {
-                                                               submit();
-                                                       }
-                                               });
-                                               
-                                               elBySel('button[data-type=submit]', content).addEventListener('click', submit);
-                                       }.bind(this),
-                                       title: Language.get('wcf.acp.devtools.project.instructions.edit')
-                               });
-                       }
-                       else {
-                               UiDialog.openStatic(dialogId);
-                       }
-               },
-               
-               /**
-                * Returns the error element for the given form field element.
-                * If `createIfNonExistent` is not given or `false`, `null` is returned
-                * if there is no error element, otherwise an empty error element
-                * is created and returned.
-                *
-                * @param       {?boolean}      createIfNonExistent
-                * @return      {?HTMLElement}
-                */
-               _getErrorElement: function(element, createIfNoNExistent) {
-                       var error = DomTraverse.nextByClass(element, 'innerError');
-                       
-                       if (error === null && createIfNoNExistent) {
-                               error = elCreate('small');
-                               error.className = 'innerError';
-                               
-                               DomUtil.insertAfter(error, element);
-                       }
-                       
-                       return error;
-               },
-               
-               /**
-                * Returns the error element for the from version form field.
-                * If `createIfNonExistent` is not given or `false`, `null` is returned
-                * if there is no error element, otherwise an empty error element
-                * is created and returned.
-                *
-                * @param       {?boolean}      createIfNonExistent
-                * @return      {?HTMLElement}
-                */
-               _getFromVersionErrorElement: function(inputField, createIfNonExistent) {
-                       return this._getErrorElement(inputField, createIfNonExistent);
-               },
-               
-               /**
-                * Returns the error element for the instruction type form field.
-                * If `createIfNonExistent` is not given or `false`, `null` is returned
-                * if there is no error element, otherwise an empty error element
-                * is created and returned.
-                *
-                * @param       {?boolean}      createIfNonExistent
-                * @return      {?HTMLElement}
-                */
-               _getInstructionsTypeErrorElement: function(createIfNonExistent) {
-                       return this._getErrorElement(this._instructionsType, createIfNonExistent);
-               },
-               
-               /**
-                * Adds an instruction after pressing ENTER in a relevant text
-                * field.
-                * 
-                * @param       {Event}         event
-                */
-               _instructionKeyPress: function(event) {
-                       if (EventKey.Enter(event)) {
-                               this._addInstruction(event);
-                       }
-               },
-               
-               /**
-                * Adds a set of instruction after pressing ENTER in a relevant
-                * text field.
-                * 
-                * @param       {Event}         event
-                */
-               _instructionsKeyPress: function(event) {
-                       if (EventKey.Enter(event)) {
-                               this._addInstructions(event);
-                       }
-               },
-               
-               /**
-                * Removes an instruction by clicking on its delete button.
-                * 
-                * @param       {Event}         event           delete button click event
-                */
-               _removeInstruction: function(event) {
-                       var instruction = event.currentTarget.closest('li');
-                       
-                       UiConfirmation.show({
-                               confirm: function() {
-                                       elRemove(instruction);
-                               },
-                               message: Language.get('wcf.acp.devtools.project.instruction.delete.confirmMessages')
-                       });
-               },
-               
-               /**
-                * Removes a set of instructions by clicking on its delete button.
-                * 
-                * @param       {Event}         event           delete button click event
-                */
-               _removeInstructions: function(event) {
-                       var instructions = event.currentTarget.closest('li');
-                       
-                       UiConfirmation.show({
-                               confirm: function() {
-                                       elRemove(instructions);
-                               },
-                               message: Language.get('wcf.acp.devtools.project.instructions.delete.confirmMessages')
-                       });
-               },
-               
-               /**
-                * Adds all necessary (hidden) form fields to the form when
-                * submitting the form.
-                */
-               _submit: function(event) {
-                       DomTraverse.childrenByTag(this._instructionsList, 'LI').forEach(function(instructions, instructionsIndex) {
-                               var namePrefix = this._formFieldId + '[' + instructionsIndex + ']';
-                               
-                               var instructionsType = elCreate('input');
-                               elAttr(instructionsType, 'type', 'hidden');
-                               elAttr(instructionsType, 'name', namePrefix + '[type]')
-                               instructionsType.value = elData(instructions, 'type');
-                               this._form.appendChild(instructionsType);
-                               
-                               if (instructionsType.value === 'update') {
-                                       var fromVersion = elCreate('input');
-                                       elAttr(fromVersion, 'type', 'hidden');
-                                       elAttr(fromVersion, 'name', this._formFieldId + '[' + instructionsIndex + '][fromVersion]')
-                                       fromVersion.value = elData(instructions, 'fromVersion');
-                                       this._form.appendChild(fromVersion);
-                               }
-                               
-                               DomTraverse.childrenByTag(elById(instructions.id + '_instructionList'), 'LI').forEach(function(instruction, instructionIndex) {
-                                       var namePrefix = this._formFieldId + '[' + instructionsIndex + '][instructions][' + instructionIndex + ']';
-                                       
-                                       for (var property of ['pip', 'value', 'runStandalone']) {
-                                               var element = elCreate('input');
-                                               elAttr(element, 'type', 'hidden');
-                                               elAttr(element, 'name', namePrefix + '[' + property + ']')
-                                               element.value = elData(instruction, property);
-                                               this._form.appendChild(element);
-                                       }
-                                       
-                                       if (_applicationPips.indexOf(elData(instruction, 'pip')) !== -1) {
-                                               var application = elCreate('input');
-                                               elAttr(application, 'type', 'hidden');
-                                               elAttr(application, 'name', namePrefix + '[application]')
-                                               application.value = elData(instruction, 'application');
-                                               this._form.appendChild(application);
-                                       }
-                               }.bind(this));
-                       }.bind(this));
-               },
-               
-               /**
-                * Toggles the visibility of the application form field based on
-                * the selected pip for the instructions with the given id.
-                *
-                * @param       {int}   instructionsId          id of the relevant instruction set
-                */
-               _toggleApplicationFormField: function(instructionsId) {
-                       var pip = elById(this._formFieldId + '_instructions' + instructionsId + '_pip').value;
-                       
-                       var valueDlClassList = elById(this._formFieldId + '_instructions' + instructionsId + '_value').closest('dl').classList;
-                       var applicationDl = elById(this._formFieldId + '_instructions' + instructionsId + '_application').closest('dl');
-                       
-                       if (_applicationPips.indexOf(pip) !== -1) {
-                               valueDlClassList.remove('col-md-9');
-                               valueDlClassList.add('col-md-7');
-                               elShow(applicationDl);
-                       }
-                       else {
-                               valueDlClassList.remove('col-md-7');
-                               valueDlClassList.add('col-md-9');
-                               elHide(applicationDl);
-                       }
-               },
-               
-               /**
-                * Toggles the visibility of the `fromVersion` form field based on
-                * the selected instructions type.
-                */
-               _toggleFromVersionFormField: function() {
-                       var instructionsTypeList = this._instructionsType.closest('dl').classList;
-                       var fromVersionDl = this._fromVersion.closest('dl');
-                       
-                       if (this._instructionsType.value === 'update') {
-                               instructionsTypeList.remove('col-md-10');
-                               instructionsTypeList.add('col-md-5');
-                               elShow(fromVersionDl);
-                       }
-                       else {
-                               instructionsTypeList.remove('col-md-5');
-                               instructionsTypeList.add('col-md-10');
-                               elHide(fromVersionDl);
-                       }
-               },
-               
-               /**
-                * Returns `true` if the currently entered update "from version"
-                * is valid. Otherwise `false` is returned and an error message
-                * is shown.
-                * 
-                * @return      {boolean}
-                */
-               _validateFromVersion: function(inputField) {
-                       var version = inputField.value;
-                       
-                       if (version === '') {
-                               this._getFromVersionErrorElement(inputField, true).textContent = Language.get('wcf.global.form.error.empty');
-                               
-                               return false;
-                       }
-                       
-                       if (version.length > 50) {
-                               this._getFromVersionErrorElement(inputField, true).textContent = Language.get('wcf.acp.devtools.project.packageVersion.error.maximumLength');
-                               
-                               return false;
-                       }
-                       
-                       // wildcard versions are checked on the server side
-                       if (version.indexOf('*') === -1) {
-                               // see `wcf\data\package\Package::isValidVersion()`
-                               if (!version.match(/^([0-9]+)\.([0-9]+)\.([0-9]+)(\ (a|alpha|b|beta|d|dev|rc|pl)\ ([0-9]+))?$/i)) {
-                                       this._getFromVersionErrorElement(inputField, true).textContent = Language.get('wcf.acp.devtools.project.packageVersion.error.format');
-                                       
-                                       return false;
-                               }
-                       }
-                       else if (!version.replace('*', '0').match(/^([0-9]+)\.([0-9]+)\.([0-9]+)(\ (a|alpha|b|beta|d|dev|rc|pl)\ ([0-9]+))?$/i)) {
-                               this._getFromVersionErrorElement(inputField, true).textContent = Language.get('wcf.acp.devtools.project.packageVersion.error.format');
-                               
-                               return false;
-                       }
-                       
-                       // remove outdated errors
-                       var error = this._getFromVersionErrorElement(inputField);
-                       if (error !== null) {
-                               elRemove(error);
-                       }
-                       
-                       return true;
-               },
-               
-               /**
-                * Returns `true` if the entered update instructions type is valid.
-                * Otherwise `false` is returned and an error message is shown.
-                * 
-                * @return      {boolean}
-                */
-               _validateInstructionsType: function() {
-                       if (this._instructionsType.value !== 'install' && this._instructionsType.value !== 'update') {
-                               if (this._instructionsType.value === '') {
-                                       this._getInstructionsTypeErrorElement(true).textContent = Language.get('wcf.global.form.error.empty');
-                               }
-                               else {
-                                       this._getInstructionsTypeErrorElement(true).textContent = Language.get('wcf.global.form.error.noValidSelection');
-                               }
-                               
-                               return false;
-                       }
-                       
-                       // there may only be one set of installation instructions 
-                       if (this._instructionsType.value === 'install') {
-                               var hasInstall = false;
-                               [].forEach.call(this._instructionsList.children, function(instructions) {
-                                       if (elData(instructions, 'type') === 'install') {
-                                               hasInstall = true;
-                                       }
-                               });
-                               
-                               if (hasInstall) {
-                                       this._getInstructionsTypeErrorElement(true).textContent = Language.get('wcf.acp.devtools.project.instructions.type.update.error.duplicate');
-                                       
-                                       return false;
-                               }
-                       }
-                       
-                       // remove outdated errors
-                       var error = this._getInstructionsTypeErrorElement();
-                       if (error !== null) {
-                               elRemove(error);
-                       }
-                       
-                       return true;
-               }
-       };
-       
-       return Instructions;
-});
-
-/**
- * Manages the packages entered in a devtools project optional package form field.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/OptionalPackages
- * @see        module:WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/OptionalPackages',['./AbstractPackageList', 'Core', 'Language'], function(AbstractPackageList, Core, Language) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function OptionalPackages(formFieldId, existingPackages) {
-               this.init(formFieldId, existingPackages);
-       };
-       Core.inherit(OptionalPackages, AbstractPackageList, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList#_populateListItem
-                */
-               _populateListItem(listItem, packageData) {
-                       OptionalPackages._super.prototype._populateListItem.call(this, listItem, packageData);
-                       
-                       listItem.innerHTML = ' ' + Language.get('wcf.acp.devtools.project.optionalPackage.optionalPackage', {
-                               file: packageData.file,
-                               packageIdentifier: packageData.packageIdentifier
-                       });
-               }
-       });
-       
-       return OptionalPackages;
-});
-
-/**
- * Manages the packages entered in a devtools project required package form field.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/RequiredPackages
- * @see        module:WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList
- * @since      5.2
- */
-define('WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/RequiredPackages',['./AbstractPackageList', 'Core', 'Language'], function(AbstractPackageList, Core, Language) {
-       "use strict";
-       
-       /**
-        * @constructor
-        */
-       function RequiredPackages(formFieldId, existingPackages) {
-               this.init(formFieldId, existingPackages);
-       };
-       Core.inherit(RequiredPackages, AbstractPackageList, {
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList#init
-                */
-               init: function(formFieldId, existingPackages) {
-                       RequiredPackages._super.prototype.init.call(this, formFieldId, existingPackages);
-                       
-                       this._minVersion = elById(this._formFieldId + '_minVersion');
-                       if (this._minVersion === null) {
-                               throw new Error("Cannot find minimum version form field for packages field with id '" + this._formFieldId + "'.");
-                       }
-                       this._minVersion.addEventListener('keypress', this._keyPress.bind(this));
-                       
-                       this._file = elById(this._formFieldId + '_file');
-                       if (this._file === null) {
-                               throw new Error("Cannot find file form field for required field with id '" + this._formFieldId + "'.");
-                       }
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList#_createSubmitFields
-                */
-               _createSubmitFields: function(listElement, index) {
-                       RequiredPackages._super.prototype._createSubmitFields.call(this, listElement, index);
-                       
-                       var minVersion = elCreate('input');
-                       elAttr(minVersion, 'type', 'hidden');
-                       elAttr(minVersion, 'name', this._formFieldId + '[' + index + '][minVersion]')
-                       minVersion.value = elData(listElement, 'min-version');
-                       this._form.appendChild(minVersion);
-                       
-                       var file = elCreate('input');
-                       elAttr(file, 'type', 'hidden');
-                       elAttr(file, 'name', this._formFieldId + '[' + index + '][file]')
-                       file.value = elData(listElement, 'file');
-                       this._form.appendChild(file);
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList#_emptyInput
-                */
-               _emptyInput() {
-                       RequiredPackages._super.prototype._emptyInput.call(this);
-                       
-                       this._minVersion.value = '';
-                       this._file.checked = false;
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList#_getInputData
-                */
-               _getInputData: function() {
-                       return Core.extend(RequiredPackages._super.prototype._getInputData.call(this), {
-                               file: this._file.checked,
-                               minVersion: this._minVersion.value
-                       });
-               },
-               
-               /**
-                * Returns the error element for the minimum version form field.
-                * If `createIfNonExistent` is not given or `false`, `null` is returned
-                * if there is no error element, otherwise an empty error element
-                * is created and returned.
-                *
-                * @param       {?boolean}      createIfNonExistent
-                * @return      {?HTMLElement}
-                */
-               _getMinVersionErrorElement: function(createIfNonExistent) {
-                       return this._getErrorElement(this._minVersion, createIfNonExistent);
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList#_populateListItem
-                */
-               _populateListItem(listItem, packageData) {
-                       RequiredPackages._super.prototype._populateListItem.call(this, listItem, packageData);
-                       
-                       elData(listItem, 'min-version', packageData.minVersion);
-                       elData(listItem, 'file', ~~packageData.file);
-                       listItem.innerHTML = ' ' + Language.get('wcf.acp.devtools.project.requiredPackage.requiredPackage', {
-                               file: ~~packageData.file,
-                               minVersion: packageData.minVersion,
-                               packageIdentifier: packageData.packageIdentifier
-                       });
-               },
-               
-               /**
-                * @see WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList#_validateInput
-                */
-               _validateInput: function() {
-                       return RequiredPackages._super.prototype._validateInput.call(this) && this._validateVersion(
-                               this._minVersion.value,
-                               this._getMinVersionErrorElement.bind(this)
-                       );
-               }
-       });
-       
-       return RequiredPackages;
-});
-
-/**
- * Default implementation for user interaction menu items used in the user profile.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLabSuite/Core/Ui/User/Profile/Menu/Item/Abstract
- */
-define('WoltLabSuite/Core/Ui/User/Profile/Menu/Item/Abstract',['Ajax', 'Dom/Util'], function(Ajax, DomUtil) {
-       "use strict";
-       
-       /**
-        * Creates a new user profile menu item.
-        * 
-        * @param       {int}           userId          user id
-        * @param       {boolean}       isActive        true if item is initially active
-        * @constructor
-        */
-       function UiUserProfileMenuItemAbstract(userId, isActive) {}
-       UiUserProfileMenuItemAbstract.prototype = {
-               /**
-                * Creates a new user profile menu item.
-                * 
-                * @param       {int}           userId          user id
-                * @param       {boolean}       isActive        true if item is initially active
-                */
-               init: function(userId, isActive) {
-                       this._userId = userId;
-                       this._isActive = (isActive !== false);
-                       
-                       this._initButton();
-                       this._updateButton();
-               },
-               
-               /**
-                * Initializes the menu item.
-                * 
-                * @protected
-                */
-               _initButton: function() {
-                       var button = elCreate('a');
-                       button.href = '#';
-                       button.addEventListener(WCF_CLICK_EVENT, this._toggle.bind(this));
-                       
-                       var listItem = elCreate('li');
-                       listItem.appendChild(button);
-                       
-                       var menu = elBySel('.userProfileButtonMenu[data-menu="interaction"]');
-                       DomUtil.prepend(listItem, menu);
-                       
-                       this._button = button;
-                       this._listItem = listItem;
-               },
-               
-               /**
-                * Handles clicks on the menu item button.
-                * 
-                * @param       {Event}         event   event object
-                * @protected
-                */
-               _toggle: function(event) {
-                       event.preventDefault();
-                       
-                       Ajax.api(this, {
-                               actionName: this._getAjaxActionName(),
-                               parameters: {
-                                       data: {
-                                               userID: this._userId
-                                       }
-                               }
-                       });
-               },
-               
-               /**
-                * Updates the button state and label.
-                * 
-                * @protected
-                */
-               _updateButton: function() {
-                       this._button.textContent = this._getLabel();
-                       this._listItem.classList[(this._isActive ? 'add' : 'remove')]('active');
-               },
-               
-               /**
-                * Returns the button label.
-                * 
-                * @return      {string}        button label
-                * @protected
-                * @abstract
-                */
-               _getLabel: function() {
-                       throw new Error("Implement me!");
-               },
-               
-               /**
-                * Returns the Ajax action name.
-                * 
-                * @return      {string}        ajax action name
-                * @protected
-                * @abstract
-                */
-               _getAjaxActionName: function() {
-                       throw new Error("Implement me!");
-               },
-               
-               /**
-                * Handles successful Ajax requests.
-                * 
-                * @protected
-                * @abstract
-                */
-               _ajaxSuccess: function() {
-                       throw new Error("Implement me!");
-               },
-               
-               /**
-                * Returns the default Ajax request data
-                * 
-                * @return      {Object}        ajax request data
-                * @protected
-                * @abstract
-                */
-               _ajaxSetup: function() {
-                       throw new Error("Implement me!");
-               }
-       };
-       
-       return UiUserProfileMenuItemAbstract;
-});
-
-define('WoltLabSuite/Core/Ui/User/Profile/Menu/Item/Follow',['Core', 'Language', 'Ui/Notification', './Abstract'], function(Core, Language, UiNotification, UiUserProfileMenuItemAbstract) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       _getLabel: function() {},
-                       _getAjaxActionName: function() {},
-                       _ajaxSuccess: function() {},
-                       _ajaxSetup: function() {},
-                       init: function() {},
-                       _initButton: function() {},
-                       _toggle: function() {},
-                       _updateButton: function() {}
-               };
-               return Fake;
-       }
-       
-       function UiUserProfileMenuItemFollow(userId, isActive) { this.init(userId, isActive); }
-       Core.inherit(UiUserProfileMenuItemFollow, UiUserProfileMenuItemAbstract, {
-               _getLabel: function() {
-                       return Language.get('wcf.user.button.' + (this._isActive ? 'un' : '') + 'follow');
-               },
-               
-               _getAjaxActionName: function() {
-                       return this._isActive ? 'unfollow' : 'follow';
-               },
-               
-               _ajaxSuccess: function(data) {
-                       this._isActive = (data.returnValues.following ? true : false);
-                       this._updateButton();
-                       
-                       UiNotification.show();
-               },
-               
-               _ajaxSetup: function() {
-                       return {
-                               data: {
-                                       className: 'wcf\\data\\user\\follow\\UserFollowAction'
-                               }
-                       };
-               }
-       });
-       
-       return UiUserProfileMenuItemFollow;
-});
-
-define('WoltLabSuite/Core/Ui/User/Profile/Menu/Item/Ignore',['Core', 'Language', 'Ui/Notification', './Abstract'], function(Core, Language, UiNotification, UiUserProfileMenuItemAbstract) {
-       "use strict";
-       
-       if (!COMPILER_TARGET_DEFAULT) {
-               var Fake = function() {};
-               Fake.prototype = {
-                       _getLabel: function() {},
-                       _getAjaxActionName: function() {},
-                       _ajaxSuccess: function() {},
-                       _ajaxSetup: function() {},
-                       init: function() {},
-                       _initButton: function() {},
-                       _toggle: function() {},
-                       _updateButton: function() {}
-               };
-               return Fake;
-       }
-       
-       function UiUserProfileMenuItemIgnore(userId, isActive) { this.init(userId, isActive); }
-       Core.inherit(UiUserProfileMenuItemIgnore, UiUserProfileMenuItemAbstract, {
-               _getLabel: function() {
-                       return Language.get('wcf.user.button.' + (this._isActive ? 'un' : '') + 'ignore');
-               },
-               
-               _getAjaxActionName: function() {
-                       return this._isActive ? 'unignore' : 'ignore';
-               },
-               
-               _ajaxSuccess: function(data) {
-                       this._isActive = (data.returnValues.isIgnoredUser ? true : false);
-                       this._updateButton();
-                       
-                       UiNotification.show();
-               },
-               
-               _ajaxSetup: function() {
-                       return {
-                               data: {
-                                       className: 'wcf\\data\\user\\ignore\\UserIgnoreAction'
-                               }
-                       };
-               }
-       });
-       
-       return UiUserProfileMenuItemIgnore;
-});
-
-/*
- * Polyfill for `Element.prototype.matches()` and `Element.prototype.closest()`
- * Copyright (c) 2015 Jonathan Neal - https://github.com/jonathantneal/closest
- * License: CC0 1.0 Universal (https://creativecommons.org/publicdomain/zero/1.0/)
- */
-(function(ELEMENT) {
-       ELEMENT.matches = ELEMENT.matches || ELEMENT.mozMatchesSelector || ELEMENT.msMatchesSelector || ELEMENT.oMatchesSelector || ELEMENT.webkitMatchesSelector;
-       
-       ELEMENT.closest = ELEMENT.closest || function closest(selector) {
-                       var element = this;
-                       
-                       while (element) {
-                               if (element.matches(selector)) {
-                                       break;
-                               }
-                               
-                               element = element.parentElement;
-                       }
-                       
-                       return element;
-               };
-}(Element.prototype));
-
-define("closest", function(){});
-
-(function(window) {
-       var orgRequire = window.require;
-       var queue = [];
-       var counter = 0;
-
-       window.orgRequire = orgRequire
-       
-       window.require = function(dependencies, callback, errBack) {
-               if (!Array.isArray(dependencies)) {
-                       return orgRequire.apply(window, arguments);
-               }
-               
-               var promise = new Promise(function (resolve, reject) {
-                       var i = counter++;
-                       queue.push(i);
-                       
-                       orgRequire(dependencies, function () {
-                               var args = arguments;
-                               
-                               queue[queue.indexOf(i)] = function() { resolve(args); };
-                               
-                               executeCallbacks();
-                       }, function (err) {
-                               queue[queue.indexOf(i)] = function() { reject(err); };
-                               
-                               executeCallbacks();
-                       });
-               });
-               
-               if (callback) {
-                       promise = promise.then(function (objects) {
-                               return callback.apply(window, objects);
-                       });
-               }
-               if (errBack) {
-                       promise.catch(errBack);
-               }
-               
-               return promise;
-       };
-       window.require.config = orgRequire.config;
-       
-       function executeCallbacks() {
-               while (queue.length) {
-                       if (typeof queue[0] !== 'function') {
-                               break;
-                       }
-                       
-                       queue.shift()();
-               }
-       }
-})(window);
-
-define("require.linearExecution", function(){});
-
+var requirejs,require,define;!function(global,Promise,undef){function commentReplace(e,t){return t||""}function hasProp(e,t){return hasOwn.call(e,t)}function getOwn(e,t){return e&&hasProp(e,t)&&e[t]}function obj(){return Object.create(null)}function eachProp(e,t){var i;for(i in e)if(hasProp(e,i)&&t(e[i],i))break}function mixin(e,t,i,n){return t&&eachProp(t,function(t,o){!i&&hasProp(e,o)||(!n||"object"!=typeof t||!t||Array.isArray(t)||"function"==typeof t||t instanceof RegExp?e[o]=t:(e[o]||(e[o]={}),mixin(e[o],t,i,n)))}),e}function getGlobal(e){if(!e)return e;var t=global;return e.split(".").forEach(function(e){t=t[e]}),t}function newContext(e){function t(e){var t,i,n=e.length;for(t=0;t<n;t++)if("."===(i=e[t]))e.splice(t,1),t-=1;else if(".."===i){if(0===t||1===t&&".."===e[2]||".."===e[t-1])continue;t>0&&(e.splice(t-1,2),t-=2)}}function i(e,i,n){var o,r,a,s,l,c,u,d,h,f,p=i&&i.split("/"),g=p,m=T.map,v=m&&m["*"];if(e&&(e=e.split("/"),c=e.length-1,T.nodeIdCompat&&jsSuffixRegExp.test(e[c])&&(e[c]=e[c].replace(jsSuffixRegExp,"")),"."===e[0].charAt(0)&&p&&(g=p.slice(0,p.length-1),e=g.concat(e)),t(e),e=e.join("/")),n&&m&&(p||v)){r=e.split("/");e:for(a=r.length;a>0;a-=1){if(l=r.slice(0,a).join("/"),p)for(s=p.length;s>0;s-=1)if((o=getOwn(m,p.slice(0,s).join("/")))&&(o=getOwn(o,l))){u=o,d=a;break e}!h&&v&&getOwn(v,l)&&(h=getOwn(v,l),f=a)}!u&&h&&(u=h,d=f),u&&(r.splice(0,d,u),e=r.join("/"))}return getOwn(T.pkgs,e)||e}function n(e){function t(){var t;return e.init&&(t=e.init.apply(global,arguments)),t||e.exports&&getGlobal(e.exports)}return t}function o(e){var t,i,n,o;for(t=0;t<queue.length;t+=1){if("string"!=typeof queue[t][0]){if(!e)break;queue[t].unshift(e),e=undef}n=queue.shift(),i=n[0],t-=1,i in D||i in k||(i in N?C.apply(undef,n):k[i]=n)}e&&(o=getOwn(T.shim,e)||{},C(e,o.deps||[],o.exportsFn))}function r(e,t){var n=function(i,r,a,s){var l,c;if(t&&o(),"string"==typeof i){if(A[i])return A[i](e);if(!((l=E(i,e,!0).id)in D))throw new Error("Not loaded: "+l);return D[l]}return i&&!Array.isArray(i)&&(c=i,i=undef,Array.isArray(r)&&(i=r,r=a,a=s),t)?n.config(c)(i,r,a):(r=r||function(){return slice.call(arguments,0)},V.then(function(){return o(),C(undef,i||[],r,a,e)}))};return n.isBrowser="undefined"!=typeof document&&"undefined"!=typeof navigator,n.nameToUrl=function(e,t,i){var o,r,a,s,l,c,u,d=getOwn(T.pkgs,e);if(d&&(e=d),u=getOwn(H,e))return n.nameToUrl(u,t,i);if(urlRegExp.test(e))l=e+(t||"");else{for(o=T.paths,r=e.split("/"),a=r.length;a>0;a-=1)if(s=r.slice(0,a).join("/"),c=getOwn(o,s)){Array.isArray(c)&&(c=c[0]),r.splice(0,a,c);break}l=r.join("/"),l+=t||(/^data\:|^blob\:|\?/.test(l)||i?"":".js"),l=("/"===l.charAt(0)||l.match(/^[\w\+\.\-]+:/)?"":T.baseUrl)+l}return T.urlArgs&&!/^blob\:/.test(l)?l+T.urlArgs(e,l):l},n.toUrl=function(t){var o,r=t.lastIndexOf("."),a=t.split("/")[0],s="."===a||".."===a;return-1!==r&&(!s||r>1)&&(o=t.substring(r,t.length),t=t.substring(0,r)),n.nameToUrl(i(t,e),o,!0)},n.defined=function(t){return E(t,e,!0).id in D},n.specified=function(t){return(t=E(t,e,!0).id)in D||t in N},n}function a(e,t,i){e&&(D[e]=i,requirejs.onResourceLoad&&requirejs.onResourceLoad(x,t.map,t.deps)),t.finished=!0,t.resolve(i)}function s(e,t){e.finished=!0,e.rejected=!0,e.reject(t)}function l(e){return function(t){return i(t,e,!0)}}function c(e){e.factoryCalled=!0;var t,i=e.map.id;try{t=x.execCb(i,e.factory,e.values,D[i])}catch(t){return s(e,t)}i?t===undef&&(e.cjsModule?t=e.cjsModule.exports:e.usingExports&&(t=D[i])):M.splice(M.indexOf(e),1),a(i,e,t)}function u(e,t){this.rejected||this.depDefined[t]||(this.depDefined[t]=!0,this.depCount+=1,this.values[t]=e,this.depending||this.depCount!==this.depMax||c(this))}function d(e,t){var i={};return i.promise=new Promise(function(t,n){i.resolve=t,i.reject=function(t){e||M.splice(M.indexOf(i),1),n(t)}}),i.map=e?t||E(e):{},i.depCount=0,i.depMax=0,i.values=[],i.depDefined=[],i.depFinished=u,i.map.pr&&(i.deps=[E(i.map.pr)]),i}function h(e,t){var i;return e?(i=e in N&&N[e])||(i=N[e]=d(e,t)):(i=d(),M.push(i)),i}function f(e,t){return function(i){e.rejected||(i.dynaId||(i.dynaId="id"+(W+=1),i.requireModules=[t]),s(e,i))}}function p(e,t,i,n){i.depMax+=1,L(e,t).then(function(e){i.depFinished(e,n)},f(i,e.id)).catch(f(i,i.map.id))}function g(e){function t(t){i||a(e,h(e),t)}var i;return t.error=function(t){h(e).reject(t)},t.fromText=function(t,n){var r=h(e),a=E(E(e).n),l=a.id;i=!0,r.factory=function(e,t){return t},n&&(t=n),hasProp(T.config,e)&&(T.config[l]=T.config[e]);try{w.exec(t)}catch(e){s(r,new Error("fromText eval for "+l+" failed: "+e))}o(l),r.deps=[a],p(a,null,r,r.deps.length)},t}function m(e,t,i){e.load(t.n,r(i),g(t.id),T)}function v(e){var t,i=e?e.indexOf("!"):-1;return i>-1&&(t=e.substring(0,i),e=e.substring(i+1,e.length)),[t,e]}function b(e,t,i){var n=e.map.id;t[n]=!0,!e.finished&&e.deps&&e.deps.forEach(function(n){var o=n.id,r=!hasProp(A,o)&&h(o,n);!r||r.finished||i[o]||(hasProp(t,o)?e.deps.forEach(function(t,i){t.id===o&&e.depFinished(D[o],i)}):b(r,t,i))}),i[n]=!0}function _(e){var t,i,n,o=[],r=1e3*T.waitSeconds,a=r&&F+r<(new Date).getTime();if(0===j&&(e?e.finished||b(e,{},{}):M.length&&M.forEach(function(e){b(e,{},{})})),a){for(i in N)n=N[i],n.finished||o.push(n.map.id);t=new Error("Timeout for modules: "+o),t.requireModules=o,w.onError(t)}else(j||M.length)&&(S||(S=!0,setTimeout(function(){S=!1,_()},70)))}function y(e){return setTimeout(function(){e.dynaId&&O[e.dynaId]||(O[e.dynaId]=!0,w.onError(e))}),e}var w,C,E,L,A,S,I,x,D=obj(),k=obj(),T={waitSeconds:7,baseUrl:"./",paths:{},bundles:{},pkgs:{},shim:{},config:{}},B=obj(),M=[],N=obj(),P=obj(),U=obj(),j=0,F=(new Date).getTime(),W=0,O=obj(),R=obj(),H=obj(),V=Promise.resolve();return I="function"==typeof importScripts?function(e){var t=e.url;R[t]||(R[t]=!0,h(e.id),importScripts(t),o(e.id))}:function(e){var t,i=e.id,n=e.url;R[n]||(R[n]=!0,t=document.createElement("script"),t.setAttribute("data-requiremodule",i),t.type=T.scriptType||"text/javascript",t.charset="utf-8",t.async=!0,j+=1,t.addEventListener("load",function(){j-=1,o(i)},!1),t.addEventListener("error",function(){j-=1;var e,n=getOwn(T.paths,i);if(n&&Array.isArray(n)&&n.length>1){t.parentNode.removeChild(t),n.shift();var o=h(i);o.map=E(i),o.map.url=w.nameToUrl(i),I(o.map)}else e=new Error("Load failed: "+i+": "+t.src),e.requireModules=[i],h(i).reject(e)},!1),t.src=n,10===document.documentMode?asap.then(function(){document.head.appendChild(t)}):document.head.appendChild(t))},L=function(e,t){var i,n,o=e.id,r=T.shim[o];if(o in k)i=k[o],delete k[o],C.apply(undef,i);else if(!(o in N))if(e.pr){if(!(n=getOwn(H,o)))return L(E(e.pr)).then(function(i){var n=e.prn?e:E(o,t,!0),r=n.id,a=getOwn(T.shim,r);return r in U||(U[r]=!0,a&&a.deps?w(a.deps,function(){m(i,n,t)}):m(i,n,t)),h(r).promise});e.url=w.nameToUrl(n),I(e)}else r&&r.deps?w(r.deps,function(){I(e)}):I(e);return h(o).promise},E=function(e,t,n){if("string"!=typeof e)return e;var o,r,a,s,c,u,d=e+" & "+(t||"")+" & "+!!n;return a=v(e),s=a[0],e=a[1],!s&&d in B?B[d]:(s&&(s=i(s,t,n),o=s in D&&D[s]),s?o&&o.normalize?(e=o.normalize(e,l(t)),u=!0):e=-1===e.indexOf("!")?i(e,t,n):e:(e=i(e,t,n),a=v(e),s=a[0],e=a[1],r=w.nameToUrl(e)),c={id:s?s+"!"+e:e,n:e,pr:s,url:r,prn:s&&u},s||(B[d]=c),c)},A={require:function(e){return r(e)},exports:function(e){var t=D[e];return void 0!==t?t:D[e]={}},module:function(e){return{id:e,uri:"",exports:A.exports(e),config:function(){return getOwn(T.config,e)||{}}}}},C=function(e,t,i,n,o){if(e){if(e in P)return;P[e]=!0}var r=h(e);return t&&!Array.isArray(t)&&(i=t,t=[]),t=t?slice.call(t,0):null,n||(hasProp(T,"defaultErrback")?T.defaultErrback&&(n=T.defaultErrback):n=y),n&&r.promise.catch(n),o=o||e,"function"==typeof i?(!t.length&&i.length&&(i.toString().replace(commentRegExp,commentReplace).replace(cjsRequireRegExp,function(e,i){t.push(i)}),t=(1===i.length?["require"]:["require","exports","module"]).concat(t)),r.factory=i,r.deps=t,r.depending=!0,t.forEach(function(i,n){var a;t[n]=a=E(i,o,!0),i=a.id,"require"===i?r.values[n]=A.require(e):"exports"===i?(r.values[n]=A.exports(e),r.usingExports=!0):"module"===i?r.values[n]=r.cjsModule=A.module(e):void 0===i?r.values[n]=void 0:p(a,o,r,n)}),r.depending=!1,r.depCount===r.depMax&&c(r)):e&&a(e,r,i),F=(new Date).getTime(),e||_(r),r.promise},w=r(null,!0),w.config=function(t){if(t.context&&t.context!==e){var i=getOwn(contexts,t.context);return i?i.req.config(t):newContext(t.context).config(t)}if(B=obj(),t.baseUrl&&"/"!==t.baseUrl.charAt(t.baseUrl.length-1)&&(t.baseUrl+="/"),"string"==typeof t.urlArgs){var o=t.urlArgs;t.urlArgs=function(e,t){return(-1===t.indexOf("?")?"?":"&")+o}}var r=T.shim,a={paths:!0,bundles:!0,config:!0,map:!0};return eachProp(t,function(e,t){a[t]?(T[t]||(T[t]={}),mixin(T[t],e,!0,!0)):T[t]=e}),t.bundles&&eachProp(t.bundles,function(e,t){e.forEach(function(e){e!==t&&(H[e]=t)})}),t.shim&&(eachProp(t.shim,function(e,t){Array.isArray(e)&&(e={deps:e}),!e.exports&&!e.init||e.exportsFn||(e.exportsFn=n(e)),r[t]=e}),T.shim=r),t.packages&&t.packages.forEach(function(e){var t,i;e="string"==typeof e?{name:e}:e,i=e.name,t=e.location,t&&(T.paths[i]=e.location),T.pkgs[i]=e.name+"/"+(e.main||"main").replace(currDirRegExp,"").replace(jsSuffixRegExp,"")}),(t.deps||t.callback)&&w(t.deps,t.callback),w},w.onError=function(e){throw e},x={id:e,defined:D,waiting:k,config:T,deferreds:N,req:w,execCb:function(e,t,i,n){return t.apply(n,i)}},contexts[e]=x,w}if(!Promise)throw new Error("No Promise implementation available");var topReq,dataMain,src,subPath,bootstrapConfig=requirejs||require,hasOwn=Object.prototype.hasOwnProperty,contexts={},queue=[],currDirRegExp=/^\.\//,urlRegExp=/^\/|\:|\?|\.js$/,commentRegExp=/\/\*[\s\S]*?\*\/|([^:"'=]|^)\/\/.*$/gm,cjsRequireRegExp=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,jsSuffixRegExp=/\.js$/,slice=Array.prototype.slice;if("function"!=typeof requirejs){var asap=Promise.resolve(void 0);requirejs=topReq=newContext("_"),"function"!=typeof require&&(require=topReq),topReq.exec=function(text){return eval(text)},topReq.contexts=contexts,define=function(){queue.push(slice.call(arguments,0))},define.amd={jQuery:!0},bootstrapConfig&&topReq.config(bootstrapConfig),topReq.isBrowser&&!contexts._.config.skipDataMain&&(dataMain=document.querySelectorAll("script[data-main]")[0],(dataMain=dataMain&&dataMain.getAttribute("data-main"))&&(dataMain=dataMain.replace(jsSuffixRegExp,""),bootstrapConfig&&bootstrapConfig.baseUrl||-1!==dataMain.indexOf("!")||(src=dataMain.split("/"),dataMain=src.pop(),subPath=src.length?src.join("/")+"/":"./",topReq.config({baseUrl:subPath})),topReq([dataMain])))}}(this,"undefined"!=typeof Promise?Promise:void 0),define("requireLib",function(){}),requirejs.config({paths:{enquire:"3rdParty/enquire",favico:"3rdParty/favico","perfect-scrollbar":"3rdParty/perfect-scrollbar",Pica:"3rdParty/pica",prism:"3rdParty/prism"},shim:{enquire:{exports:"enquire"},favico:{exports:"Favico"},"perfect-scrollbar":{exports:"PerfectScrollbar"}},map:{"*":{Ajax:"WoltLabSuite/Core/Ajax",AjaxJsonp:"WoltLabSuite/Core/Ajax/Jsonp",AjaxRequest:"WoltLabSuite/Core/Ajax/Request",CallbackList:"WoltLabSuite/Core/CallbackList",ColorUtil:"WoltLabSuite/Core/ColorUtil",Core:"WoltLabSuite/Core/Core",DateUtil:"WoltLabSuite/Core/Date/Util",Devtools:"WoltLabSuite/Core/Devtools",Dictionary:"WoltLabSuite/Core/Dictionary","Dom/ChangeListener":"WoltLabSuite/Core/Dom/Change/Listener","Dom/Traverse":"WoltLabSuite/Core/Dom/Traverse","Dom/Util":"WoltLabSuite/Core/Dom/Util",Environment:"WoltLabSuite/Core/Environment",EventHandler:"WoltLabSuite/Core/Event/Handler",EventKey:"WoltLabSuite/Core/Event/Key",Language:"WoltLabSuite/Core/Language",List:"WoltLabSuite/Core/List",ObjectMap:"WoltLabSuite/Core/ObjectMap",Permission:"WoltLabSuite/Core/Permission",StringUtil:"WoltLabSuite/Core/StringUtil","Ui/Alignment":"WoltLabSuite/Core/Ui/Alignment","Ui/CloseOverlay":"WoltLabSuite/Core/Ui/CloseOverlay","Ui/Confirmation":"WoltLabSuite/Core/Ui/Confirmation","Ui/Dialog":"WoltLabSuite/Core/Ui/Dialog","Ui/Notification":"WoltLabSuite/Core/Ui/Notification","Ui/ReusableDropdown":"WoltLabSuite/Core/Ui/Dropdown/Reusable","Ui/Screen":"WoltLabSuite/Core/Ui/Screen","Ui/Scroll":"WoltLabSuite/Core/Ui/Scroll","Ui/SimpleDropdown":"WoltLabSuite/Core/Ui/Dropdown/Simple","Ui/TabMenu":"WoltLabSuite/Core/Ui/TabMenu",Upload:"WoltLabSuite/Core/Upload",User:"WoltLabSuite/Core/User"}},waitSeconds:0}),define("jquery",[],function(){return window.jQuery}),define("require.config",function(){}),function(e,t){e.elAttr=function(e,t,i){if(void 0===i)return e.getAttribute(t)||"";e.setAttribute(t,i)},e.elAttrBool=function(e,t){var i=elAttr(e,t);return"1"===i||"true"===i},e.elByClass=function(e,i){return(i||t).getElementsByClassName(e)},e.elById=function(e){return t.getElementById(e)},e.elBySel=function(e,i){return(i||t).querySelector(e)},e.elBySelAll=function(e,i,n){var o=(i||t).querySelectorAll(e);return"function"==typeof n&&Array.prototype.forEach.call(o,n),o},e.elByTag=function(e,i){return(i||t).getElementsByTagName(e)},e.elCreate=function(e){return t.createElement(e)},e.elClosest=function(e,t){if(!(e instanceof Node))throw new TypeError("Provided element is not a Node.");return e.nodeType===Node.TEXT_NODE&&null===(e=e.parentNode)?null:("string"!=typeof t&&(t=""),0===t.length?e:e.closest(t))},e.elData=function(e,t,i){if(t="data-"+t,void 0===i)return e.getAttribute(t)||"";e.setAttribute(t,i)},e.elDataBool=function(e,t){var i=elData(e,t);return"1"===i||"true"===i},e.elHide=function(e){e.style.setProperty("display","none","")},e.elIsHidden=function(e){return"none"===e.style.getPropertyValue("display")},e.elInnerError=function(e,t,i){var n=e.parentNode;if(null===n)throw new Error("Only elements that have a parent element or document are valid.");if("string"!=typeof t){if(void 0!==t&&null!==t&&!1!==t)throw new TypeError("The error message must be a string; `false`, `null` or `undefined` can be used as a substitute for an empty string.");t=""}var o=e.nextElementSibling;return null!==o&&"SMALL"===o.nodeName&&o.classList.contains("innerError")||(""===t?o=null:(o=elCreate("small"),o.className="innerError",n.insertBefore(o,e.nextSibling))),""===t?null!==o&&(n.removeChild(o),o=null):o[i?"innerHTML":"textContent"]=t,o},e.elRemove=function(e){e.parentNode.removeChild(e)},e.elShow=function(e){e.style.removeProperty("display")},e.elToggle=function(e){"none"===e.style.getPropertyValue("display")?elShow(e):elHide(e)},e.forEach=function(e,t){for(var i=0,n=e.length;i<n;i++)t(e[i],i)},e.objOwns=function(e,t){return e.hasOwnProperty(t)};"touchstart"in t.documentElement||"ontouchstart"in e||navigator.MaxTouchPoints>0||navigator.msMaxTouchPoints;Object.defineProperty(e,"WCF_CLICK_EVENT",{value:"click"}),function(){function t(){e.history.state&&e.history.state.name&&"initial"!==e.history.state.name?(e.history.replaceState({name:"skip",depth:++i},""),e.history.back(),setTimeout(t,1)):e.history.replaceState({name:"initial"},"")}var i=0;t(),e.addEventListener("popstate",function(t){t.state&&t.state.name&&"skip"===t.state.name&&e.history.go(t.state.depth)})}(),e.String.prototype.hashCode=function(){var e,t=0;if(this.length)for(var i=0,n=this.length;i<n;i++)e=this.charCodeAt(i),t=(t<<5)-t+e,t&=t;return t}}(window,document),define("wcf.globalHelper",function(){}),define("WoltLabSuite/Core/Core",[],function(){"use strict";var e=function(e){return"object"==typeof e&&(Array.isArray(e)||n.isPlainObject(e))?t(e):e},t=function(t){if(!t)return null;if(Array.isArray(t))return t.slice();var i={};for(var n in t)t.hasOwnProperty(n)&&void 0!==t[n]&&(i[n]=e(t[n]));return i},i="wsc"+window.WCF_PATH.hashCode()+"-",n={clone:function(t){return e(t)},convertLegacyUrl:function(e){return e.replace(/^index\.php\/(.*?)\/\?/,function(e,t){var i=t.split(/([A-Z][a-z0-9]+)/);t="";for(var n=0,o=i.length;n<o;n++){var r=i[n].trim();r.length&&(t.length&&(t+="-"),t+=r.toLowerCase())}return"index.php?"+t+"/&"})},extend:function(e){e=e||{};for(var t=this.clone(e),i=1,n=arguments.length;i<n;i++){var o=arguments[i];if(o)for(var r in o)objOwns(o,r)&&(Array.isArray(o[r])||"object"!=typeof o[r]?t[r]=o[r]:this.isPlainObject(o[r])?t[r]=this.extend(e[r],o[r]):t[r]=o[r])}return t},inherit:function(e,t,i){if(void 0===e||null===e)throw new TypeError("The constructor must not be undefined or null.");if(void 0===t||null===t)throw new TypeError("The super constructor must not be undefined or null.");if(void 0===t.prototype)throw new TypeError("The super constructor must have a prototype.");e._super=t,e.prototype=n.extend(Object.create(t.prototype,{constructor:{configurable:!0,enumerable:!1,value:e,writable:!0}}),i||{})},isPlainObject:function(e){return"object"==typeof e&&null!==e&&!e.nodeType&&Object.getPrototypeOf(e)===Object.prototype},getType:function(e){return Object.prototype.toString.call(e).replace(/^\[object (.+)\]$/,"$1")},getUuid:function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){var t=16*Math.random()|0;return("x"==e?t:3&t|8).toString(16)})},serialize:function(e,t){var i=[];for(var n in e)if(objOwns(e,n)){var o=t?t+"["+n+"]":n,r=e[n];"object"==typeof r?i.push(this.serialize(r,o)):i.push(encodeURIComponent(o)+"="+encodeURIComponent(r))}return i.join("&")},triggerEvent:function(e,t){var i;try{i=new Event(t,{bubbles:!0,cancelable:!0})}catch(e){i=document.createEvent("Event"),i.initEvent(t,!0,!0)}e.dispatchEvent(i)},getStoragePrefix:function(){return i}};return n}),define("WoltLabSuite/Core/Dictionary",["Core"],function(e){"use strict";function t(){this._dictionary=i?new Map:{}}var i=objOwns(window,"Map")&&"function"==typeof window.Map;return t.prototype={set:function(e,t){if("number"==typeof e&&(e=e.toString()),"string"!=typeof e)throw new TypeError("Only strings can be used as keys, rejected '"+e+"' ("+typeof e+").");i?this._dictionary.set(e,t):this._dictionary[e]=t},delete:function(e){"number"==typeof e&&(e=e.toString()),i?this._dictionary.delete(e):this._dictionary[e]=void 0},has:function(e){return"number"==typeof e&&(e=e.toString()),i?this._dictionary.has(e):objOwns(this._dictionary,e)&&void 0!==this._dictionary[e]},get:function(e){if("number"==typeof e&&(e=e.toString()),this.has(e))return i?this._dictionary.get(e):this._dictionary[e]},forEach:function(e){if("function"!=typeof e)throw new TypeError("forEach() expects a callback as first parameter.");if(i)this._dictionary.forEach(e);else for(var t=Object.keys(this._dictionary),n=0,o=t.length;n<o;n++)e(this._dictionary[t[n]],t[n])},merge:function(){for(var e=0,i=arguments.length;e<i;e++){var n=arguments[e];if(!(n instanceof t))throw new TypeError("Expected an object of type Dictionary, but argument "+e+" is not.");n.forEach(function(e,t){this.set(t,e)}.bind(this))}},toObject:function(){if(!i)return e.clone(this._dictionary);var t={};return this._dictionary.forEach(function(e,i){t[i]=e}),t}},t.fromObject=function(e){var i=new t;for(var n in e)objOwns(e,n)&&i.set(n,e[n]);return i},Object.defineProperty(t.prototype,"size",{enumerable:!1,configurable:!0,get:function(){return i?this._dictionary.size:Object.keys(this._dictionary).length}}),t}),define("WoltLabSuite/Core/Template.grammar",["require"],function(e){var t=function(e,t,i,n){for(i=i||{},n=e.length;n--;i[e[n]]=t);return i},i=[2,37],n=[5,9,11,12,13,18,19,21,22,23,25,26,27,28,30,31,32,33,35,37,39],o=[1,24],r=[1,25],a=[1,31],s=[1,29],l=[1,30],c=[1,26],u=[1,27],d=[1,33],h=[11,12,15,40,41,45,47,49,50,52],f=[9,11,12,13,18,19,21,23,26,28,30,31,32,33,35,37],p=[11,12,15,40,41,44,45,46,47,49,50,52],g=[18,35,37],m=[12,15],v={trace:function(){},yy:{},symbols_:{error:2,TEMPLATE:3,CHUNK_STAR:4,EOF:5,CHUNK_STAR_repetition0:6,CHUNK:7,PLAIN_ANY:8,T_LITERAL:9,COMMAND:10,T_ANY:11,T_WS:12,"{if":13,COMMAND_PARAMETERS:14,"}":15,COMMAND_repetition0:16,COMMAND_option0:17,"{/if}":18,"{include":19,COMMAND_PARAMETER_LIST:20,"{implode":21,"{/implode}":22,"{foreach":23,COMMAND_option1:24,"{/foreach}":25,"{lang}":26,"{/lang}":27,"{":28,VARIABLE:29,"{#":30,"{@":31,"{ldelim}":32,"{rdelim}":33,ELSE:34,"{else}":35,ELSE_IF:36,"{elseif":37,FOREACH_ELSE:38,"{foreachelse}":39,T_VARIABLE:40,T_VARIABLE_NAME:41,VARIABLE_repetition0:42,VARIABLE_SUFFIX:43,"[":44,"]":45,".":46,"(":47,VARIABLE_SUFFIX_option0:48,")":49,"=":50,COMMAND_PARAMETER_VALUE:51,T_QUOTED_STRING:52,COMMAND_PARAMETERS_repetition_plus0:53,COMMAND_PARAMETER:54,$accept:0,$end:1},terminals_:{2:"error",5:"EOF",9:"T_LITERAL",11:"T_ANY",12:"T_WS",13:"{if",15:"}",18:"{/if}",19:"{include",21:"{implode",22:"{/implode}",23:"{foreach",25:"{/foreach}",26:"{lang}",27:"{/lang}",28:"{",30:"{#",31:"{@",32:"{ldelim}",33:"{rdelim}",35:"{else}",37:"{elseif",39:"{foreachelse}",40:"T_VARIABLE",41:"T_VARIABLE_NAME",44:"[",45:"]",46:".",47:"(",49:")",50:"=",52:"T_QUOTED_STRING"},productions_:[0,[3,2],[4,1],[7,1],[7,1],[7,1],[8,1],[8,1],[10,7],[10,3],[10,5],[10,6],[10,3],[10,3],[10,3],[10,3],[10,1],[10,1],[34,2],[36,4],[38,2],[29,3],[43,3],[43,2],[43,3],[20,5],[20,3],[51,1],[51,1],[14,1],[54,1],[54,1],[54,1],[54,1],[54,1],[54,1],[54,3],[6,0],[6,2],[16,0],[16,2],[17,0],[17,1],[24,0],[24,1],[42,0],[42,2],[48,0],[48,1],[53,1],[53,2]],performAction:function(e,t,i,n,o,r,a){var s=r.length-1;switch(o){case 1:return r[s-1]+";";case 2:var l=r[s].reduce(function(e,t){return t.encode&&!e[1]?e[0]+=" + '"+t.value:t.encode&&e[1]?e[0]+=t.value:!t.encode&&e[1]?e[0]+="' + "+t.value:t.encode||e[1]||(e[0]+=" + "+t.value),e[1]=t.encode,e},["''",!1]);l[1]&&(l[0]+="'"),this.$=l[0];break;case 3:case 4:this.$={encode:!0,value:r[s].replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/(\r\n|\n|\r)/g,"\\n")};break;case 5:this.$={encode:!1,value:r[s]};break;case 8:this.$="(function() { if ("+r[s-5]+") { return "+r[s-3]+"; } "+r[s-2].join(" ")+" "+(r[s-1]||"")+" return ''; })()";break;case 9:if(!r[s-1].file)throw new Error("Missing parameter file");this.$=r[s-1].file+".fetch(v)";break;case 10:if(!r[s-3].from)throw new Error("Missing parameter from");if(!r[s-3].item)throw new Error("Missing parameter item");r[s-3].glue||(r[s-3].glue="', '"),this.$="(function() { return "+r[s-3].from+".map(function(item) { v["+r[s-3].item+"] = item; return "+r[s-1]+"; }).join("+r[s-3].glue+"); })()";break;case 11:if(!r[s-4].from)throw new Error("Missing parameter from");if(!r[s-4].item)throw new Error("Missing parameter item");this.$="(function() {var looped = false, result = '';if ("+r[s-4].from+" instanceof Array) {for (var i = 0; i < "+r[s-4].from+".length; i++) { looped = true;v["+r[s-4].key+"] = i;v["+r[s-4].item+"] = "+r[s-4].from+"[i];result += "+r[s-2]+";}} else {for (var key in "+r[s-4].from+") {if (!"+r[s-4].from+".hasOwnProperty(key)) continue;looped = true;v["+r[s-4].key+"] = key;v["+r[s-4].item+"] = "+r[s-4].from+"[key];result += "+r[s-2]+";}}return (looped ? result : "+(r[s-1]||"''")+"); })()";break;case 12:this.$="Language.get("+r[s-1]+", v)";break;case 13:this.$="StringUtil.escapeHTML("+r[s-1]+")";break;case 14:this.$="StringUtil.formatNumeric("+r[s-1]+")";break;case 15:this.$=r[s-1];break;case 16:this.$="'{'";break;case 17:this.$="'}'";break;case 18:this.$="else { return "+r[s]+"; }";break;case 19:this.$="else if ("+r[s-2]+") { return "+r[s]+"; }";break;case 20:this.$=r[s];break;case 21:this.$="v['"+r[s-1]+"']"+r[s].join("");break;case 22:this.$=r[s-2]+r[s-1]+r[s];break;case 23:this.$="['"+r[s]+"']";break;case 24:case 36:this.$=r[s-2]+(r[s-1]||"")+r[s];break;case 25:this.$=r[s],this.$[r[s-4]]=r[s-2];break;case 26:this.$={},this.$[r[s-2]]=r[s];break;case 29:this.$=r[s].join("");break;case 37:case 39:case 45:this.$=[];break;case 38:case 40:case 46:case 50:r[s-1].push(r[s]);break;case 49:this.$=[r[s]]}},table:[t([5,9,11,12,13,19,21,23,26,28,30,31,32,33],i,{3:1,4:2,6:3}),{1:[3]},{5:[1,4]},t([5,18,22,25,27,35,37,39],[2,2],{7:5,8:6,10:8,9:[1,7],11:[1,9],12:[1,10],13:[1,11],19:[1,12],21:[1,13],23:[1,14],26:[1,15],28:[1,16],30:[1,17],31:[1,18],32:[1,19],33:[1,20]}),{1:[2,1]},t(n,[2,38]),t(n,[2,3]),t(n,[2,4]),t(n,[2,5]),t(n,[2,6]),t(n,[2,7]),{11:o,12:r,14:21,29:28,40:a,41:s,47:l,50:c,52:u,53:22,54:23},{20:32,41:d},{20:34,41:d},{20:35,41:d},t([9,11,12,13,19,21,23,26,27,28,30,31,32,33],i,{6:3,4:36}),{29:37,40:a},{29:38,40:a},{29:39,40:a},t(n,[2,16]),t(n,[2,17]),{15:[1,40]},t([15,45,49],[2,29],{29:28,54:41,11:o,12:r,40:a,41:s,47:l,50:c,52:u}),t(h,[2,49]),t(h,[2,30]),t(h,[2,31]),t(h,[2,32]),t(h,[2,33]),t(h,[2,34]),t(h,[2,35]),{11:o,12:r,14:42,29:28,40:a,41:s,47:l,50:c,52:u,53:22,54:23},{41:[1,43]},{15:[1,44]},{50:[1,45]},{15:[1,46]},{15:[1,47]},{27:[1,48]},{15:[1,49]},{15:[1,50]},{15:[1,51]},t(f,i,{6:3,4:52}),t(h,[2,50]),{49:[1,53]},t(p,[2,45],{42:54}),t(n,[2,9]),{29:57,40:a,51:55,52:[1,56]},t([9,11,12,13,19,21,22,23,26,28,30,31,32,33],i,{6:3,4:58}),t([9,11,12,13,19,21,23,25,26,28,30,31,32,33,39],i,{6:3,4:59}),t(n,[2,12]),t(n,[2,13]),t(n,[2,14]),t(n,[2,15]),t(g,[2,39],{16:60}),t(h,[2,36]),t([11,12,15,40,41,45,49,50,52],[2,21],{43:61,44:[1,62],46:[1,63],47:[1,64]}),{12:[1,65],15:[2,26]},t(m,[2,27]),t(m,[2,28]),{22:[1,66]},{24:67,25:[2,43],38:68,39:[1,69]},{17:70,18:[2,41],34:72,35:[1,74],36:71,37:[1,73]},t(p,[2,46]),{11:o,12:r,14:75,29:28,40:a,41:s,47:l,50:c,52:u,53:22,54:23},{41:[1,76]},{11:o,12:r,14:78,29:28,40:a,41:s,47:l,48:77,49:[2,47],50:c,52:u,53:22,54:23},{20:79,41:d},t(n,[2,10]),{25:[1,80]},{25:[2,44]},t([9,11,12,13,19,21,23,25,26,28,30,31,32,33],i,{6:3,4:81}),{18:[1,82]},t(g,[2,40]),{18:[2,42]},{11:o,12:r,14:83,29:28,40:a,41:s,47:l,50:c,52:u,53:22,54:23},t([9,11,12,13,18,19,21,23,26,28,30,31,32,33],i,{6:3,4:84}),{45:[1,85]},t(p,[2,23]),{49:[1,86]},{49:[2,48]},{15:[2,25]},t(n,[2,11]),{25:[2,20]},t(n,[2,8]),{15:[1,87]},{18:[2,18]},t(p,[2,22]),t(p,[2,24]),t(f,i,{6:3,4:88}),t(g,[2,19])],defaultActions:{4:[2,1],68:[2,44],72:[2,42],78:[2,48],79:[2,25],81:[2,20],84:[2,18]},parseError:function(e,t){function i(e,t){this.message=e,this.hash=t}if(!t.recoverable)throw i.prototype=Error,new i(e,t);this.trace(e)},parse:function(e){var t=this,i=[0],n=[null],o=[],r=this.table,a="",s=0,l=0,c=0,u=o.slice.call(arguments,1),d=Object.create(this.lexer),h={yy:{}};for(var f in this.yy)Object.prototype.hasOwnProperty.call(this.yy,f)&&(h.yy[f]=this.yy[f]);d.setInput(e,h.yy),h.yy.lexer=d,h.yy.parser=this,void 0===d.yylloc&&(d.yylloc={});var p=d.yylloc;o.push(p);var g=d.options&&d.options.ranges;"function"==typeof h.yy.parseError?this.parseError=h.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var m,v,b,_,y,w,C,E,L,A=function(){var e;return e=d.lex()||1,"number"!=typeof e&&(e=t.symbols_[e]||e),e},S={};;){if(b=i[i.length-1],this.defaultActions[b]?_=this.defaultActions[b]:(null!==m&&void 0!==m||(m=A()),_=r[b]&&r[b][m]),void 0===_||!_.length||!_[0]){var I="";L=[];for(w in r[b])this.terminals_[w]&&w>2&&L.push("'"+this.terminals_[w]+"'");I=d.showPosition?"Parse error on line "+(s+1)+":\n"+d.showPosition()+"\nExpecting "+L.join(", ")+", got '"+(this.terminals_[m]||m)+"'":"Parse error on line "+(s+1)+": Unexpected "+(1==m?"end of input":"'"+(this.terminals_[m]||m)+"'"),this.parseError(I,{text:d.match,token:this.terminals_[m]||m,line:d.yylineno,loc:p,expected:L})}if(_[0]instanceof Array&&_.length>1)throw new Error("Parse Error: multiple actions possible at state: "+b+", token: "+m);switch(_[0]){case 1:i.push(m),n.push(d.yytext),o.push(d.yylloc),i.push(_[1]),m=null,v?(m=v,v=null):(l=d.yyleng,a=d.yytext,s=d.yylineno,p=d.yylloc,c>0&&c--);break;case 2:if(C=this.productions_[_[1]][1],S.$=n[n.length-C],S._$={first_line:o[o.length-(C||1)].first_line,last_line:o[o.length-1].last_line,first_column:o[o.length-(C||1)].first_column,last_column:o[o.length-1].last_column},g&&(S._$.range=[o[o.length-(C||1)].range[0],o[o.length-1].range[1]]),void 0!==(y=this.performAction.apply(S,[a,l,s,h.yy,_[1],n,o].concat(u))))return y;C&&(i=i.slice(0,-1*C*2),n=n.slice(0,-1*C),o=o.slice(0,-1*C)),i.push(this.productions_[_[1]][0]),n.push(S.$),o.push(S._$),E=r[i[i.length-2]][i[i.length-1]],i.push(E);break;case 3:return!0}}return!0}},b=function(){return{EOF:1,parseError:function(e,t){if(!this.yy.parser)throw new Error(e);this.yy.parser.parseError(e,t)},setInput:function(e,t){return this.yy=t||this.yy||{},this._input=e,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var e=this._input[0];return this.yytext+=e,this.yyleng++,this.offset++,this.match+=e,this.matched+=e,e.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),e},unput:function(e){var t=e.length,i=e.split(/(?:\r\n?|\n)/g);this._input=e+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-t),this.offset-=t;var n=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),i.length-1&&(this.yylineno-=i.length-1);var o=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:i?(i.length===n.length?this.yylloc.first_column:0)+n[n.length-i.length].length-i[0].length:this.yylloc.first_column-t},this.options.ranges&&(this.yylloc.range=[o[0],o[0]+this.yyleng-t]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(e){this.unput(this.match.slice(e))},pastInput:function(){var e=this.matched.substr(0,this.matched.length-this.match.length);return(e.length>20?"...":"")+e.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var e=this.match;return e.length<20&&(e+=this._input.substr(0,20-e.length)),(e.substr(0,20)+(e.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var e=this.pastInput(),t=new Array(e.length+1).join("-");return e+this.upcomingInput()+"\n"+t+"^"},test_match:function(e,t){var i,n,o;if(this.options.backtrack_lexer&&(o={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(o.yylloc.range=this.yylloc.range.slice(0))),n=e[0].match(/(?:\r\n?|\n).*/g),n&&(this.yylineno+=n.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:n?n[n.length-1].length-n[n.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+e[0].length},this.yytext+=e[0],this.match+=e[0],this.matches=e,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(e[0].length),this.matched+=e[0],i=this.performAction.call(this,this.yy,this,t,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),i)return i;if(this._backtrack){for(var r in o)this[r]=o[r];return!1}return!1},next:function(){if(this.done)return this.EOF;this._input||(this.done=!0);var e,t,i,n;this._more||(this.yytext="",this.match="");for(var o=this._currentRules(),r=0;r<o.length;r++)if((i=this._input.match(this.rules[o[r]]))&&(!t||i[0].length>t[0].length)){if(t=i,n=r,this.options.backtrack_lexer){if(!1!==(e=this.test_match(i,o[r])))return e;if(this._backtrack){t=!1;continue}return!1}if(!this.options.flex)break}return t?!1!==(e=this.test_match(t,o[n]))&&e:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){
+var e=this.next();return e||this.lex()},begin:function(e){this.conditionStack.push(e)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(e){return e=this.conditionStack.length-1-Math.abs(e||0),e>=0?this.conditionStack[e]:"INITIAL"},pushState:function(e){this.begin(e)},stateStackSize:function(){return this.conditionStack.length},options:{},performAction:function(e,t,i,n){switch(i){case 0:break;case 1:return t.yytext=t.yytext.substring(9,t.yytext.length-10),9;case 2:case 3:return 52;case 4:return 40;case 5:return 41;case 6:return 46;case 7:return 44;case 8:return 45;case 9:return 47;case 10:return 49;case 11:return 50;case 12:return 32;case 13:return 33;case 14:return this.begin("command"),30;case 15:return this.begin("command"),31;case 16:return this.begin("command"),13;case 17:case 18:return this.begin("command"),37;case 19:return 35;case 20:return 18;case 21:return 26;case 22:return 27;case 23:return this.begin("command"),19;case 24:return this.begin("command"),21;case 25:return 22;case 26:return this.begin("command"),23;case 27:return 39;case 28:return 25;case 29:return this.begin("command"),28;case 30:return this.popState(),15;case 31:return 12;case 32:return 5;case 33:return 11}},rules:[/^(?:\{\*[\s\S]*?\*\})/,/^(?:\{literal\}[\s\S]*?\{\/literal\})/,/^(?:"([^"]|\\\.)*")/,/^(?:'([^']|\\\.)*')/,/^(?:\$)/,/^(?:[_a-zA-Z][_a-zA-Z0-9]*)/,/^(?:\.)/,/^(?:\[)/,/^(?:\])/,/^(?:\()/,/^(?:\))/,/^(?:=)/,/^(?:\{ldelim\})/,/^(?:\{rdelim\})/,/^(?:\{#)/,/^(?:\{@)/,/^(?:\{if )/,/^(?:\{else if )/,/^(?:\{elseif )/,/^(?:\{else\})/,/^(?:\{\/if\})/,/^(?:\{lang\})/,/^(?:\{\/lang\})/,/^(?:\{include )/,/^(?:\{implode )/,/^(?:\{\/implode\})/,/^(?:\{foreach )/,/^(?:\{foreachelse\})/,/^(?:\{\/foreach\})/,/^(?:\{(?!\s))/,/^(?:\})/,/^(?:\s+)/,/^(?:$)/,/^(?:[^{])/],conditions:{command:{rules:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33],inclusive:!0},INITIAL:{rules:[0,1,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,31,32,33],inclusive:!0}}}}();return v.lexer=b,v}),define("WoltLabSuite/Core/NumberUtil",[],function(){"use strict";return{round:function(e,t){return void 0===t||0==+t?Math.round(e):(e=+e,t=+t,isNaN(e)||"number"!=typeof t||t%1!=0?NaN:(e=e.toString().split("e"),e=Math.round(+(e[0]+"e"+(e[1]?+e[1]-t:-t))),e=e.toString().split("e"),+(e[0]+"e"+(e[1]?+e[1]+t:t))))}}}),define("WoltLabSuite/Core/StringUtil",["Language","./NumberUtil"],function(e,t){"use strict";return{addThousandsSeparator:function(t){return void 0===e&&(e=require("Language")),String(t).replace(/(^-?\d{1,3}|\d{3})(?=(?:\d{3})+(?:$|\.))/g,"$1"+e.get("wcf.global.thousandsSeparator"))},escapeHTML:function(e){return String(e).replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")},escapeRegExp:function(e){return String(e).replace(/([.*+?^=!:${}()|[\]\/\\])/g,"\\$1")},formatNumeric:function(i,n){void 0===e&&(e=require("Language")),i=String(t.round(i,n||-2));var o=i.split(".");return i=this.addThousandsSeparator(o[0]),o.length>1&&(i+=e.get("wcf.global.decimalPoint")+o[1]),i=i.replace("-","−")},lcfirst:function(e){return String(e).substring(0,1).toLowerCase()+e.substring(1)},ucfirst:function(e){return String(e).substring(0,1).toUpperCase()+e.substring(1)},unescapeHTML:function(e){return String(e).replace(/&amp;/g,"&").replace(/&quot;/g,'"').replace(/&lt;/g,"<").replace(/&gt;/g,">")},shortUnit:function(e){var i="";return e>=1e6?(e/=1e6,e=e>10?Math.floor(e):t.round(e,-1),i="M"):e>=1e3&&(e/=1e3,e=e>10?Math.floor(e):t.round(e,-1),i="k"),this.formatNumeric(e)+i}}}),define("WoltLabSuite/Core/Template",["./Template.grammar","./StringUtil","Language"],function(e,t,i){"use strict";function n(){this.yy={}}function o(n){void 0===i&&(i=require("Language")),void 0===t&&(t=require("StringUtil"));try{n=e.parse(n),n="var tmp = {};\nfor (var key in v) tmp[key] = v[key];\nv = tmp;\nv.__wcf = window.WCF; v.__window = window;\nreturn "+n,this.fetch=new Function("StringUtil","Language","v",n).bind(void 0,t,i)}catch(e){throw console.debug(e.message),e}}return n.prototype=e,e.Parser=n,e=new n,Object.defineProperty(o,"callbacks",{enumerable:!1,configurable:!1,get:function(){throw new Error("WCF.Template.callbacks is no longer supported")},set:function(e){throw new Error("WCF.Template.callbacks is no longer supported")}}),o.prototype={fetch:function(e){throw new Error("This Template is not initialized.")}},o}),define("WoltLabSuite/Core/Language",["Dictionary","./Template"],function(e,t){"use strict";var i=new e;return{addObject:function(t){i.merge(e.fromObject(t))},add:function(e,t){i.set(e,t)},get:function(e,n){n||(n={});var o=i.get(e);if(void 0===o)return e;if(void 0===t&&(t=require("WoltLabSuite/Core/Template")),"string"==typeof o){try{i.set(e,new t(o))}catch(n){i.set(e,new t("{literal}"+o.replace(/\{\/literal\}/g,"{/literal}{ldelim}/literal}{literal}")+"{/literal}"))}o=i.get(e)}return o instanceof t&&(o=o.fetch(n)),o}}}),define("WoltLabSuite/Core/CallbackList",["Dictionary"],function(e){"use strict";function t(){this._dictionary=new e}return t.prototype={add:function(e,t){if("function"!=typeof t)throw new TypeError("Expected a valid callback as second argument for identifier '"+e+"'.");this._dictionary.has(e)||this._dictionary.set(e,[]),this._dictionary.get(e).push(t)},remove:function(e){this._dictionary.delete(e)},forEach:function(e,t){if(null===e)this._dictionary.forEach(function(e,i){e.forEach(t)});else{var i=this._dictionary.get(e);void 0!==i&&i.forEach(t)}}},t}),define("WoltLabSuite/Core/Dom/Change/Listener",["CallbackList"],function(e){"use strict";var t=new e,i=!1;return{add:t.add.bind(t),remove:t.remove.bind(t),trigger:function(){if(!i)try{i=!0,t.forEach(null,function(e){e()})}finally{i=!1}}}}),define("WoltLabSuite/Core/Environment",[],function(){"use strict";var e="other",t="none",i="desktop",n=!1;return{setup:function(){if("object"==typeof window.chrome)e="chrome";else for(var o=window.getComputedStyle(document.documentElement),r=0,a=o.length;r<a;r++){var s=o[r];0===s.indexOf("-ms-")?e="microsoft":0===s.indexOf("-moz-")?e="firefox":"firefox"!==e&&0===s.indexOf("-webkit-")&&(e="safari")}var l=window.navigator.userAgent.toLowerCase();-1!==l.indexOf("crios")?(e="chrome",i="ios"):/(?:iphone|ipad|ipod)/.test(l)?(e="safari",i="ios"):-1!==l.indexOf("android")?i="android":-1!==l.indexOf("iemobile")&&(e="microsoft",i="windows"),"desktop"!==i||-1===l.indexOf("mobile")&&-1===l.indexOf("tablet")||(i="mobile"),t="redactor",n=!!("ontouchstart"in window)||!!("msMaxTouchPoints"in window.navigator)&&window.navigator.msMaxTouchPoints>0||window.DocumentTouch&&document instanceof DocumentTouch},browser:function(){return e},editor:function(){return t},platform:function(){return i},touch:function(){return n}}}),define("WoltLabSuite/Core/Dom/Util",["Environment","StringUtil"],function(e,t){"use strict";function i(e,t,i){if(!t.contains(e))throw new Error("Ancestor element does not contain target element.");for(var n,o=i+"Sibling";null!==e&&e!==t;){if(null!==e[i+"ElementSibling"])return!1;if(e[o])for(n=e[o];n;){if(""!==n.textContent.trim())return!1;n=n[o]}e=e.parentNode}return!0}var n=0,o={createFragmentFromHtml:function(e){var t=elCreate("div");this.setInnerHtml(t,e);for(var i=document.createDocumentFragment();t.childNodes.length;)i.appendChild(t.childNodes[0]);return i},getUniqueId:function(){var e;do{e="wcf"+n++}while(null!==elById(e));return e},identify:function(e){if(!(e instanceof Element))throw new TypeError("Expected a valid DOM element as argument.");var t=elAttr(e,"id");return t||(t=this.getUniqueId(),elAttr(e,"id",t)),t},outerHeight:function(e,t){t=t||window.getComputedStyle(e);var i=e.offsetHeight;return i+=~~t.marginTop+~~t.marginBottom},outerWidth:function(e,t){t=t||window.getComputedStyle(e);var i=e.offsetWidth;return i+=~~t.marginLeft+~~t.marginRight},outerDimensions:function(e){var t=window.getComputedStyle(e);return{height:this.outerHeight(e,t),width:this.outerWidth(e,t)}},offset:function(e){var t=e.getBoundingClientRect();return{top:Math.round(t.top+(window.scrollY||window.pageYOffset)),left:Math.round(t.left+(window.scrollX||window.pageXOffset))}},prepend:function(e,t){0===t.childNodes.length?t.appendChild(e):t.insertBefore(e,t.childNodes[0])},insertAfter:function(e,t){null!==t.nextSibling?t.parentNode.insertBefore(e,t.nextSibling):t.parentNode.appendChild(e)},setStyles:function(e,t){var i=!1;for(var n in t)t.hasOwnProperty(n)&&(/ !important$/.test(t[n])?(i=!0,t[n]=t[n].replace(/ !important$/,"")):i=!1,"important"!==e.style.getPropertyPriority(n)||i||e.style.removeProperty(n),e.style.setProperty(n,t[n],i?"important":""))},styleAsInt:function(e,t){var i=e.getPropertyValue(t);return null===i?0:parseInt(i)},setInnerHtml:function(e,t){e.innerHTML=t;for(var i,n,o=elBySelAll("script",e),r=0,a=o.length;r<a;r++)n=o[r],i=elCreate("script"),n.src?i.src=n.src:i.textContent=n.textContent,e.appendChild(i),elRemove(n)},insertHtml:function(e,t,i){var n=elCreate("div");if(this.setInnerHtml(n,e),n.childNodes.length){var o=n.childNodes[0];switch(i){case"append":t.appendChild(o);break;case"after":this.insertAfter(o,t);break;case"prepend":this.prepend(o,t);break;case"before":t.parentNode.insertBefore(o,t);break;default:throw new Error("Unknown insert method '"+i+"'.")}for(var r;n.childNodes.length;)r=n.childNodes[0],this.insertAfter(r,o),o=r}},contains:function(e,t){for(;null!==t;)if(t=t.parentNode,e===t)return!0;return!1},getDataAttributes:function(e,i,n,o){i=i||"",/^data-/.test(i)||(i="data-"+i),n=!0===n,o=!0===o;for(var r,a,s,l={},c=0,u=e.attributes.length;c<u;c++)if(r=e.attributes[c],0===r.name.indexOf(i)){if(a=r.name.replace(new RegExp("^"+i),""),n){s=a.split("-"),a="";for(var d=0,h=s.length;d<h;d++)a.length&&(o&&"id"===s[d]?s[d]="ID":s[d]=t.ucfirst(s[d])),a+=s[d]}l[a]=r.value}return l},unwrapChildNodes:function(e){for(var t=e.parentNode;e.childNodes.length;)t.insertBefore(e.childNodes[0],e);elRemove(e)},replaceElement:function(e,t){for(;e.childNodes.length;)t.appendChild(e.childNodes[0]);e.parentNode.insertBefore(t,e),elRemove(e)},isAtNodeStart:function(e,t){return i(e,t,"previous")},isAtNodeEnd:function(e,t){return i(e,t,"next")},getFixedParent:function(e){for(;e&&e!==document.body;){if("fixed"===window.getComputedStyle(e).getPropertyValue("position"))return e;e=e.offsetParent}return null}};return window.bc_wcfDomUtil=o,o}),define("WoltLabSuite/Core/ObjectMap",[],function(){"use strict";function e(){this._map=t?new WeakMap:{key:[],value:[]}}var t=objOwns(window,"WeakMap")&&"function"==typeof window.WeakMap;return e.prototype={set:function(e,i){if("object"!=typeof e||null===e)throw new TypeError("Only objects can be used as key");if("object"!=typeof i||null===i)throw new TypeError("Only objects can be used as value");t?this._map.set(e,i):(this._map.key.push(e),this._map.value.push(i))},delete:function(e){if(t)this._map.delete(e);else{var i=this._map.key.indexOf(e);this._map.key.splice(i),this._map.value.splice(i)}},has:function(e){return t?this._map.has(e):-1!==this._map.key.indexOf(e)},get:function(e){if(t)return this._map.get(e);var i=this._map.key.indexOf(e);return-1!==i?this._map.value[i]:void 0}},e}),define("WoltLabSuite/Core/Dom/Traverse",[],function(){"use strict";var e=[function(e,t){return!0},function(e,t){return e.matches(t)},function(e,t){return e.classList.contains(t)},function(e,t){return e.nodeName===t}],t=function(t,i,n){if(!(t instanceof Element))throw new TypeError("Expected a valid element as first argument.");for(var o=[],r=0;r<t.childElementCount;r++)e[i](t.children[r],n)&&o.push(t.children[r]);return o},i=function(t,i,n,o){if(!(t instanceof Element))throw new TypeError("Expected a valid element as first argument.");for(t=t.parentNode;t instanceof Element;){if(t===o)return null;if(e[i](t,n))return t;t=t.parentNode}return null},n=function(t,i,n,o){if(!(t instanceof Element))throw new TypeError("Expected a valid element as first argument.");return t instanceof Element&&null!==t[i]&&e[n](t[i],o)?t[i]:null};return{childBySel:function(e,i){return t(e,1,i)[0]||null},childByClass:function(e,i){return t(e,2,i)[0]||null},childByTag:function(e,i){return t(e,3,i)[0]||null},childrenBySel:function(e,i){return t(e,1,i)},childrenByClass:function(e,i){return t(e,2,i)},childrenByTag:function(e,i){return t(e,3,i)},parentBySel:function(e,t,n){return i(e,1,t,n)},parentByClass:function(e,t,n){return i(e,2,t,n)},parentByTag:function(e,t,n){return i(e,3,t,n)},next:function(e){return n(e,"nextElementSibling",0,null)},nextBySel:function(e,t){return n(e,"nextElementSibling",1,t)},nextByClass:function(e,t){return n(e,"nextElementSibling",2,t)},nextByTag:function(e,t){return n(e,"nextElementSibling",3,t)},prev:function(e){return n(e,"previousElementSibling",0,null)},prevBySel:function(e,t){return n(e,"previousElementSibling",1,t)},prevByClass:function(e,t){return n(e,"previousElementSibling",2,t)},prevByTag:function(e,t){return n(e,"previousElementSibling",3,t)}}}),define("WoltLabSuite/Core/Ui/Confirmation",["Core","Language","Ui/Dialog"],function(e,t,i){"use strict";var n=!1,o=null,r=null,a={},s=null;return{show:function(t){if(void 0===i&&(i=require("Ui/Dialog")),!n){if(a=e.extend({cancel:null,confirm:null,legacyCallback:null,message:"",messageIsHtml:!1,parameters:{},template:""},t),a.message="string"==typeof a.message?a.message.trim():"",!a.message.length)throw new Error("Expected a non-empty string for option 'message'.");if("function"!=typeof a.confirm&&"function"!=typeof a.legacyCallback)throw new TypeError("Expected a valid callback for option 'confirm'.");null===r&&this._createDialog(),r.innerHTML="string"==typeof a.template?a.template.trim():"",a.messageIsHtml?s.innerHTML=a.message:s.textContent=a.message,n=!0,i.open(this)}},_dialogSetup:function(){return{id:"wcfSystemConfirmation",options:{onClose:this._onClose.bind(this),onShow:this._onShow.bind(this),title:t.get("wcf.global.confirmation.title")}}},getContentElement:function(){return r},_createDialog:function(){var e=elCreate("div");elAttr(e,"id","wcfSystemConfirmation"),e.classList.add("systemConfirmation"),s=elCreate("p"),e.appendChild(s),r=elCreate("div"),elAttr(r,"id","wcfSystemConfirmationContent"),e.appendChild(r);var n=elCreate("div");n.classList.add("formSubmit"),e.appendChild(n),o=elCreate("button"),o.classList.add("buttonPrimary"),o.textContent=t.get("wcf.global.confirmation.confirm"),o.addEventListener(WCF_CLICK_EVENT,this._confirm.bind(this)),n.appendChild(o);var a=elCreate("button");a.textContent=t.get("wcf.global.confirmation.cancel"),a.addEventListener(WCF_CLICK_EVENT,function(){i.close("wcfSystemConfirmation")}),n.appendChild(a),document.body.appendChild(e)},_confirm:function(){"function"==typeof a.legacyCallback?a.legacyCallback("confirm",a.parameters,r):a.confirm(a.parameters,r),n=!1,i.close("wcfSystemConfirmation")},_onClose:function(){n&&(o.blur(),n=!1,"function"==typeof a.legacyCallback?a.legacyCallback("cancel",a.parameters,r):"function"==typeof a.cancel&&a.cancel(a.parameters))},_onShow:function(){o.blur(),o.focus()}}}),define("WoltLabSuite/Core/Ui/Screen",["Core","Dictionary","Environment"],function(e,t,i){"use strict";var n=null,o=new t,r=0,a=null,s=0,l=0,c=t.fromObject({"screen-xs":"(max-width: 544px)","screen-sm":"(min-width: 545px) and (max-width: 768px)","screen-sm-down":"(max-width: 768px)","screen-sm-up":"(min-width: 545px)","screen-sm-md":"(min-width: 545px) and (max-width: 1024px)","screen-md":"(min-width: 769px) and (max-width: 1024px)","screen-md-down":"(max-width: 1024px)","screen-md-up":"(min-width: 769px)","screen-lg":"(min-width: 1025px)"}),u=new t;return{on:function(t,i){var n=e.getUuid(),o=this._getQueryObject(t);return"function"==typeof i.match&&o.callbacksMatch.set(n,i.match),"function"==typeof i.unmatch&&o.callbacksUnmatch.set(n,i.unmatch),"function"==typeof i.setup&&(o.mql.matches?i.setup():o.callbacksSetup.set(n,i.setup)),n},remove:function(e,t){var i=this._getQueryObject(e);i.callbacksMatch.delete(t),i.callbacksUnmatch.delete(t),i.callbacksSetup.delete(t)},is:function(e){return this._getQueryObject(e).mql.matches},scrollDisable:function(){if(0===r){s=document.body.scrollTop,a="body",s||(s=document.documentElement.scrollTop,a="documentElement");var e=elById("pageContainer");"ios"===i.platform()?(e.style.setProperty("position","relative",""),e.style.setProperty("top","-"+s+"px","")):e.style.setProperty("margin-top","-"+s+"px",""),document.documentElement.classList.add("disableScrolling")}r++},scrollEnable:function(){if(r&&0===--r){document.documentElement.classList.remove("disableScrolling");var e=elById("pageContainer");"ios"===i.platform()?(e.style.removeProperty("position"),e.style.removeProperty("top")):e.style.removeProperty("margin-top"),s&&(document[a].scrollTop=~~s)}},pageOverlayOpen:function(){0===l&&document.documentElement.classList.add("pageOverlayActive"),l++},pageOverlayClose:function(){l&&0===--l&&document.documentElement.classList.remove("pageOverlayActive")},pageOverlayIsActive:function(){return l>0},setDialogContainer:function(e){n=e},_getQueryObject:function(e){if("string"!=typeof e||""===e.trim())throw new TypeError("Expected a non-empty string for parameter 'query'.");u.has(e)&&(e=u.get(e)),c.has(e)&&(e=c.get(e));var i=o.get(e);return i||(i={callbacksMatch:new t,callbacksUnmatch:new t,callbacksSetup:new t,mql:window.matchMedia(e)},i.mql.addListener(this._mqlChange.bind(this)),o.set(e,i),e!==i.mql.media&&u.set(i.mql.media,e)),i},_mqlChange:function(e){var i=this._getQueryObject(e.media);e.matches?i.callbacksSetup.size?(i.callbacksSetup.forEach(function(e){e()}),i.callbacksSetup=new t):i.callbacksMatch.forEach(function(e){e()}):i.callbacksUnmatch.forEach(function(e){e()})}}}),define("WoltLabSuite/Core/Event/Key",[],function(){"use strict";function e(e,t,i){if(!(e instanceof Event))throw new TypeError("Expected a valid event when testing for key '"+t+"'.");return e.key===t||e.which===i}return{ArrowDown:function(t){return e(t,"ArrowDown",40)},ArrowLeft:function(t){return e(t,"ArrowLeft",37)},ArrowRight:function(t){return e(t,"ArrowRight",39)},ArrowUp:function(t){return e(t,"ArrowUp",38)},Comma:function(t){return e(t,",",44)},End:function(t){return e(t,"End",35)},Enter:function(t){return e(t,"Enter",13)},Escape:function(t){return e(t,"Escape",27)},Home:function(t){return e(t,"Home",36)},Space:function(t){return e(t,"Space",32)},Tab:function(t){return e(t,"Tab",9)}}}),define("WoltLabSuite/Core/Ui/Alignment",["Core","Language","Dom/Traverse","Dom/Util"],function(e,t,i,n){"use strict";return{set:function(o,r,a){a=e.extend({verticalOffset:0,pointer:!1,pointerOffset:4,pointerClassNames:[],refDimensionsElement:null,horizontal:"left",vertical:"bottom",allowFlip:"both"},a),Array.isArray(a.pointerClassNames)&&a.pointerClassNames.length===(a.pointer?1:2)||(a.pointerClassNames=[]),-1===["left","right","center"].indexOf(a.horizontal)&&(a.horizontal="left"),"bottom"!==a.vertical&&(a.vertical="top"),-1===["both","horizontal","vertical","none"].indexOf(a.allowFlip)&&(a.allowFlip="both"),n.setStyles(o,{bottom:"auto !important",left:"0 !important",right:"auto !important",top:"0 !important",visibility:"hidden !important"});var s=n.outerDimensions(o),l=n.outerDimensions(a.refDimensionsElement instanceof Element?a.refDimensionsElement:r),c=n.offset(r),u=window.innerHeight,d=document.body.clientWidth,h={result:null},f=!1;if("center"===a.horizontal&&(f=!0,h=this._tryAlignmentHorizontal(a.horizontal,s,l,c,d),h.result||("both"===a.allowFlip||"horizontal"===a.allowFlip?a.horizontal="left":h.result=!0)),"rtl"===t.get("wcf.global.pageDirection")&&(a.horizontal="left"===a.horizontal?"right":"left"),!h.result){var p=h;if(h=this._tryAlignmentHorizontal(a.horizontal,s,l,c,d),!h.result&&("both"===a.allowFlip||"horizontal"===a.allowFlip)){var g=this._tryAlignmentHorizontal("left"===a.horizontal?"right":"left",s,l,c,d);g.result?h=g:f&&(h=p)}}var m=h.left,v=h.right,b=this._tryAlignmentVertical(a.vertical,s,l,c,u,a.verticalOffset);if(!b.result&&("both"===a.allowFlip||"vertical"===a.allowFlip)){var _=this._tryAlignmentVertical("top"===a.vertical?"bottom":"top",s,l,c,u,a.verticalOffset);_.result&&(b=_)}var y=b.bottom,w=b.top;if(a.pointer){var C=i.childrenByClass(o,"elementPointer");if(null===(C=C[0]||null))throw new Error("Expected the .elementPointer element to be a direct children.");"center"===h.align?(C.classList.add("center"),C.classList.remove("left"),C.classList.remove("right")):(C.classList.add(h.align),C.classList.remove("center"),C.classList.remove("left"===h.align?"right":"left")),"top"===b.align?C.classList.add("flipVertical"):C.classList.remove("flipVertical")}else if(2===a.pointerClassNames.length){o.classList["auto"===w?"add":"remove"](a.pointerClassNames[0]),o.classList["auto"===m?"add":"remove"](a.pointerClassNames[1])}"auto"!==y&&(y=Math.round(y)+"px"),"auto"!==m&&(m=Math.ceil(m)+"px"),"auto"!==v&&(v=Math.floor(v)+"px"),"auto"!==w&&(w=Math.round(w)+"px"),n.setStyles(o,{bottom:y,left:m,right:v,top:w}),elShow(o),o.style.removeProperty("visibility")},_tryAlignmentHorizontal:function(e,t,i,n,o){var r="auto",a="auto",s=!0;return"left"===e?(r=n.left)+t.width>o&&(s=!1):"right"===e?n.left+i.width<t.width?s=!1:(a=o-(n.left+i.width))<0&&(s=!1):(r=n.left+i.width/2-t.width/2,((r=~~r)<0||r+t.width>o)&&(s=!1)),{align:e,left:r,right:a,result:s}},_tryAlignmentVertical:function(e,t,i,n,o,r){var a="auto",s="auto",l=!0;if("top"===e){var c=document.body.clientHeight;a=c-n.top+r,c-(a+t.height)<(window.scrollY||window.pageYOffset)&&(l=!1)}else(s=n.top+i.height+r)+t.height-(window.scrollY||window.pageYOffset)>o&&(l=!1);return{align:e,bottom:a,top:s,result:l}}}}),define("WoltLabSuite/Core/Ui/CloseOverlay",["CallbackList"],function(e){"use strict";var t=new e,i={setup:function(){document.body.addEventListener(WCF_CLICK_EVENT,this.execute.bind(this))},add:t.add.bind(t),remove:t.remove.bind(t),execute:function(){t.forEach(null,function(e){e()})}};return i.setup(),i}),define("WoltLabSuite/Core/Ui/Dropdown/Simple",["CallbackList","Core","Dictionary","EventKey","Ui/Alignment","Dom/ChangeListener","Dom/Traverse","Dom/Util","Ui/CloseOverlay"],function(e,t,i,n,o,r,a,s,l){"use strict";var c=null,u=new e,d=!1,h=new i,f=new i,p=null,g=null,m="";return{setup:function(){d||(d=!0,p=elCreate("div"),p.className="dropdownMenuContainer",document.body.appendChild(p),c=elByClass("dropdownToggle"),this.initAll(),l.add("WoltLabSuite/Core/Ui/Dropdown/Simple",this.closeAll.bind(this)),r.add("WoltLabSuite/Core/Ui/Dropdown/Simple",this.initAll.bind(this)),document.addEventListener("scroll",this._onScroll.bind(this)),window.bc_wcfSimpleDropdown=this,g=this._dropdownMenuKeyDown.bind(this))},initAll:function(){for(var e=0,t=c.length;e<t;e++)this.init(c[e],!1)},init:function(e,i){if(this.setup(),elAttr(e,"role","button"),elAttr(e,"tabindex","0"),elAttr(e,"aria-haspopup",!0),elAttr(e,"aria-expanded",!1),e.classList.contains("jsDropdownEnabled")||elData(e,"target"))return!1;var n=a.parentByClass(e,"dropdown");if(null===n)throw new Error("Invalid dropdown passed, button '"+s.identify(e)+"' does not have a parent with .dropdown.");var o=a.nextByClass(e,"dropdownMenu");if(null===o)throw new Error("Invalid dropdown passed, button '"+s.identify(e)+"' does not have a menu as next sibling.");p.appendChild(o);var r=s.identify(n);if(!h.has(r)&&(e.classList.add("jsDropdownEnabled"),e.addEventListener(WCF_CLICK_EVENT,this._toggle.bind(this)),e.addEventListener("keydown",this._handleKeyDown.bind(this)),h.set(r,n),f.set(r,o),r.match(/^wcf\d+$/)||elData(o,"source",r),o.childElementCount&&o.children[0].classList.contains("scrollableDropdownMenu"))){o=o.children[0],elData(o,"scroll-to-active",!0);var l=null,c=null;o.addEventListener("wheel",function(e){null===l&&(l=o.clientHeight),null===c&&(c=o.scrollHeight),e.deltaY<0&&0===o.scrollTop?e.preventDefault():e.deltaY>0&&o.scrollTop+l===c&&e.preventDefault()},{passive:!1})}elData(e,"target",r),i&&setTimeout(function(){elData(e,"dropdown-lazy-init",i instanceof MouseEvent),t.triggerEvent(e,WCF_CLICK_EVENT),setTimeout(function(){e.removeAttribute("data-dropdown-lazy-init")},10)},10)},initFragment:function(e,t){this.setup();var i=s.identify(e);h.has(i)||(h.set(i,e),p.appendChild(t),f.set(i,t))},registerCallback:function(e,t){u.add(e,t)},getDropdown:function(e){return h.get(e)},getDropdownMenu:function(e){return f.get(e)},toggleDropdown:function(e,t,i){this._toggle(null,e,t,i)},setAlignment:function(e,t,i){var n,r=elBySel(".dropdownToggle",e);null!==r&&r.parentNode.classList.contains("inputAddonTextarea")&&(n=r),o.set(t,i||e,{pointerClassNames:["dropdownArrowBottom","dropdownArrowRight"],refDimensionsElement:n||null,horizontal:"right"===elData(t,"dropdown-alignment-horizontal")?"right":"left",vertical:"top"===elData(t,"dropdown-alignment-vertical")?"top":"bottom",allowFlip:elData(t,"dropdown-allow-flip")||"both"})},setAlignmentById:function(e){var t=h.get(e);if(void 0===t)throw new Error("Unknown dropdown identifier '"+e+"'.");var i=f.get(e);this.setAlignment(t,i)},isOpen:function(e){var t=f.get(e);return void 0!==t&&t.classList.contains("dropdownOpen")},open:function(e,t){var i=f.get(e);void 0===i||i.classList.contains("dropdownOpen")||this.toggleDropdown(e,void 0,t)},close:function(e){var t=h.get(e);void 0!==t&&(t.classList.remove("dropdownOpen"),f.get(e).classList.remove("dropdownOpen"))},closeAll:function(){h.forEach(function(e,t){e.classList.contains("dropdownOpen")&&(e.classList.remove("dropdownOpen"),f.get(t).classList.remove("dropdownOpen"),this._notifyCallbacks(t,"close"))}.bind(this))},destroy:function(e){if(!h.has(e))return!1;try{this.close(e),elRemove(f.get(e))}catch(e){}return f.delete(e),h.delete(e),!0},_onDialogScroll:function(e){for(var t=e.currentTarget,i=elBySelAll(".dropdown.dropdownOpen",t),n=0,o=i.length;n<o;n++){var r=i[n],a=s.identify(r),l=s.offset(r),c=s.offset(t);l.top+r.clientHeight<=c.top?this.toggleDropdown(a):l.top>=c.top+t.offsetHeight?this.toggleDropdown(a):l.left<=c.left?this.toggleDropdown(a):l.left>=c.left+t.offsetWidth?this.toggleDropdown(a):this.setAlignment(h.get(a),f.get(a))}},_onScroll:function(){h.forEach(function(e,t){if(e.classList.contains("dropdownOpen"))if(elDataBool(e,"is-overlay-dropdown-button"))this.setAlignment(e,f.get(t));else{var i=f.get(e.id);elDataBool(i,"dropdown-ignore-page-scroll")||this.close(t)}}.bind(this))},_notifyCallbacks:function(e,t){u.forEach(e,function(i){i(e,t)})},_toggle:function(e,t,i,n){null!==e&&(e.preventDefault(),e.stopPropagation(),t=elData(e.currentTarget,"target"),void 0===n&&e instanceof MouseEvent&&(n=!0));var o=h.get(t),r=!1;if(void 0!==o){var s;if(e&&(s=e.currentTarget,parent=s.parentNode,parent!==o&&(parent.classList.add("dropdown"),parent.id=o.id,o.classList.remove("dropdown"),o.id="",o=parent,h.set(t,parent))),void 0===n&&(s=o.closest(".dropdownToggle"),s||!(s=elBySel(".dropdownToggle",o))&&o.id&&(s=elBySel('[data-target="'+o.id+'"]')),s&&elDataBool(s,"dropdown-lazy-init")&&(n=!0)),elDataBool(o,"dropdown-prevent-toggle")&&o.classList.contains("dropdownOpen")&&(r=!0),""===elData(o,"is-overlay-dropdown-button")){var l=a.parentByClass(o,"dialogContent");elData(o,"is-overlay-dropdown-button",null!==l),null!==l&&l.addEventListener("scroll",this._onDialogScroll.bind(this))}}return m="",h.forEach(function(e,o){var a=f.get(o);if(e.classList.contains("dropdownOpen"))if(!1===r){e.classList.remove("dropdownOpen"),a.classList.remove("dropdownOpen");var s=elBySel(".dropdownToggle",e);s&&elAttr(s,"aria-expanded",!1),this._notifyCallbacks(o,"close")}else m=t;else if(o===t&&a.childElementCount>0){m=t,e.classList.add("dropdownOpen"),a.classList.add("dropdownOpen");var s=elBySel(".dropdownToggle",e);if(s&&elAttr(s,"aria-expanded",!0),a.childElementCount&&elDataBool(a.children[0],"scroll-to-active")){var l=a.children[0];l.removeAttribute("data-scroll-to-active");for(var c=null,u=0,d=l.childElementCount;u<d;u++)if(l.children[u].classList.contains("active")){c=l.children[u];break}c&&(l.scrollTop=Math.max(c.offsetTop+c.clientHeight-a.clientHeight,0))}var h=elBySel(".scrollableDropdownMenu",a);null!==h&&h.classList[h.scrollHeight>h.clientHeight?"add":"remove"]("forceScrollbar"),this._notifyCallbacks(o,"open");var p=null;n||(elAttr(a,"role","menu"),elAttr(a,"tabindex",-1),a.removeEventListener("keydown",g),a.addEventListener("keydown",g),elBySelAll("li",a,function(e){e.clientHeight&&(null===p?p=e:e.classList.contains("active")&&(p=e),elAttr(e,"role","menuitem"),elAttr(e,"tabindex",-1))})),this.setAlignment(e,a,i),null!==p&&p.focus()}}.bind(this)),window.WCF.Dropdown.Interactive.Handler.closeAll(),null===e},_handleKeyDown:function(e){(n.Enter(e)||n.Space(e))&&(e.preventDefault(),this._toggle(e))},_dropdownMenuKeyDown:function(e){var t,i,o=document.activeElement;if("LI"===o.nodeName)if(n.ArrowDown(e)||n.ArrowUp(e)||n.End(e)||n.Home(e)){e.preventDefault();var r=Array.prototype.slice.call(elBySelAll("li",o.closest(".dropdownMenu")));(n.ArrowUp(e)||n.End(e))&&r.reverse();var a=null,s=function(e){return!e.classList.contains("dropdownDivider")&&e.clientHeight>0},l=r.indexOf(o);(n.End(e)||n.Home(e))&&(l=-1);for(var c=l+1;c<r.length;c++)if(s(r[c])){a=r[c];break}if(null===a)for(c=0;c<r.length;c++)if(s(r[c])){a=r[c];break}a.focus()}else if(n.Enter(e)||n.Space(e)){e.preventDefault();var u=o;1!==u.childElementCount||"SPAN"!==u.children[0].nodeName&&"A"!==u.children[0].nodeName||(u=u.children[0]),i=h.get(m),t=elBySel(".dropdownToggle",i),require(["Core"],function(e){var n=elData(i,"a11y-mouse-event")||"click";e.triggerEvent(u,n),t&&t.focus()})}else(n.Escape(e)||n.Tab(e))&&(e.preventDefault(),i=h.get(m),t=elBySel(".dropdownToggle",i),null!==t||i.classList.contains("dropdown")||(t=i),this._toggle(null,m),t&&t.focus())}}}),define("WoltLabSuite/Core/Devtools",[],function(){"use strict";return{help:function(){},toggleEditorAutosave:function(){},toggleEventLogging:function(){},_internal_:{enable:function(){},editorAutosave:function(){},eventLog:function(){}}}}),define("WoltLabSuite/Core/Event/Handler",["Core","Devtools","Dictionary"],function(e,t,i){"use strict";var n=new i;return{add:function(t,o,r){if("function"!=typeof r)throw new TypeError("[WoltLabSuite/Core/Event/Handler] Expected a valid callback for '"+o+"@"+t+"'.");var a=n.get(t);void 0===a&&(a=new i,n.set(t,a));var s=a.get(o);void 0===s&&(s=new i,a.set(o,s));var l=e.getUuid();return s.set(l,r),l},fire:function(e,i,o){t._internal_.eventLog(e,i),o=o||{};var r=n.get(e);if(void 0!==r){var a=r.get(i);void 0!==a&&a.forEach(function(e){e(o)})}},remove:function(e,t,i){var o=n.get(e);if(void 0!==o){var r=o.get(t);void 0!==r&&r.delete(i)}},removeAll:function(e,t){"string"!=typeof t&&(t=void 0);var i=n.get(e);void 0!==i&&(void 0===t?n.delete(e):i.delete(t))},removeAllBySuffix:function(e,t){var i=n.get(e);if(void 0!==i){t="_"+t;var o=-1*t.length;i.forEach(function(i,n){n.substr(o)===t&&this.removeAll(e,n)}.bind(this))}}}}),define("WoltLabSuite/Core/List",[],function(){"use strict";function e(){this._set=t?new Set:[]}var t=objOwns(window,"Set")&&"function"==typeof window.Set;return e.prototype={add:function(e){t?this._set.add(e):this.has(e)||this._set.push(e)},clear:function(){t?this._set.clear():this._set=[]},delete:function(e){if(t)return this._set.delete(e);var i=this._set.indexOf(e);return-1!==i&&(this._set.splice(i,1),!0)},forEach:function(e){if(t)this._set.forEach(e);else for(var i=0,n=this._set.length;i<n;i++)e(this._set[i])},has:function(e){return t?this._set.has(e):-1!==this._set.indexOf(e)}},Object.defineProperty(e.prototype,"size",{enumerable:!1,configurable:!0,get:function(){return t?this._set.size:this._set.length}}),e}),
+define("WoltLabSuite/Core/Ui/Dialog",["Ajax","Core","Dictionary","Environment","Language","ObjectMap","Dom/ChangeListener","Dom/Traverse","Dom/Util","Ui/Confirmation","Ui/Screen","Ui/SimpleDropdown","EventHandler","List","EventKey"],function(e,t,i,n,o,r,a,s,l,c,u,d,h,f,p){"use strict";var g=null,m=null,v=null,b=new i,_=!1,y=new r,w=new i,C=null,E=null,L=elByClass("jsStaticDialog"),A=["onBeforeClose","onClose","onShow"],S=["number","password","search","tel","text","url"],I=['a[href]:not([tabindex^="-"]):not([inert])','area[href]:not([tabindex^="-"]):not([inert])',"input:not([disabled]):not([inert])","select:not([disabled]):not([inert])","textarea:not([disabled]):not([inert])","button:not([disabled]):not([inert])",'iframe:not([tabindex^="-"]):not([inert])','audio:not([tabindex^="-"]):not([inert])','video:not([tabindex^="-"]):not([inert])','[contenteditable]:not([tabindex^="-"]):not([inert])','[tabindex]:not([tabindex^="-"]):not([inert])'];return{setup:function(){void 0===e&&(e=require("Ajax")),v=elCreate("div"),v.classList.add("dialogOverlay"),elAttr(v,"aria-hidden","true"),v.addEventListener("mousedown",this._closeOnBackdrop.bind(this)),v.addEventListener("wheel",function(e){e.target===v&&e.preventDefault()},{passive:!1}),elById("content").appendChild(v),E=function(e){return 27!==e.keyCode||"INPUT"===e.target.nodeName||"TEXTAREA"===e.target.nodeName||(this.close(g),!1)}.bind(this),u.on("screen-xs",{match:function(){_=!0},unmatch:function(){_=!1},setup:function(){_=!0}}),this._initStaticDialogs(),a.add("Ui/Dialog",this._initStaticDialogs.bind(this)),u.setDialogContainer(v),"ios"===n.platform()&&window.addEventListener("resize",function(){b.forEach(function(e){elAttrBool(e.dialog,"aria-hidden")||this.rebuild(elData(e.dialog,"id"))}.bind(this))}.bind(this))},_initStaticDialogs:function(){for(var e,t,i;L.length;)e=L[0],e.classList.remove("jsStaticDialog"),(i=elData(e,"dialog-id"))&&(t=elById(i))&&function(e,t){t.classList.remove("jsStaticDialogContent"),elData(t,"is-static-dialog",!0),elHide(t),e.addEventListener(WCF_CLICK_EVENT,function(e){e.preventDefault(),this.openStatic(t.id,null,{title:elData(t,"title")})}.bind(this))}.bind(this)(e,t)},open:function(i,n){var o=y.get(i);if(t.isPlainObject(o))return this.openStatic(o.id,n);if("function"!=typeof i._dialogSetup)throw new Error("Callback object does not implement the method '_dialogSetup()'.");var r=i._dialogSetup();if(!t.isPlainObject(r))throw new Error("Expected an object literal as return value of '_dialogSetup()'.");o={id:r.id};var a=!0;if(void 0===r.source){var s=elById(r.id);if(null===s)throw new Error("Element id '"+r.id+"' is invalid and no source attribute was given. If you want to use the `html` argument instead, please add `source: null` to your dialog configuration.");r.source=document.createDocumentFragment(),r.source.appendChild(s),s.removeAttribute("id"),elShow(s)}else if(null===r.source)r.source=n;else if("function"==typeof r.source)r.source();else if(t.isPlainObject(r.source)){if("string"!=typeof n||""===n.trim())return e.api(this,r.source.data,function(e){e.returnValues&&"string"==typeof e.returnValues.template&&(this.open(i,e.returnValues.template),"function"==typeof r.source.after&&r.source.after(b.get(r.id).content,e))}.bind(this)),{};r.source=n}else{if("string"==typeof r.source){var s=elCreate("div");elAttr(s,"id",r.id),l.setInnerHtml(s,r.source),r.source=document.createDocumentFragment(),r.source.appendChild(s)}if(!r.source.nodeType||r.source.nodeType!==Node.DOCUMENT_FRAGMENT_NODE)throw new Error("Expected at least a document fragment as 'source' attribute.");a=!1}return y.set(i,o),w.set(r.id,i),this.openStatic(r.id,r.source,r.options,a)},openStatic:function(e,i,r,a){u.pageOverlayOpen(),"desktop"!==n.platform()&&(this.isOpen(e)||u.scrollDisable()),b.has(e)?this._updateDialog(e,i):(r=t.extend({backdropCloseOnClick:!0,closable:!0,closeButtonLabel:o.get("wcf.global.button.close"),closeConfirmMessage:"",disableContentPadding:!1,title:"",onBeforeClose:null,onClose:null,onShow:null},r),r.closable||(r.backdropCloseOnClick=!1),r.closeConfirmMessage&&(r.onBeforeClose=function(e){c.show({confirm:this.close.bind(this,e),message:r.closeConfirmMessage})}.bind(this)),this._createDialog(e,i,r));var s=b.get(e);return"ios"===n.platform()&&window.setTimeout(function(){var e=elBySel("input, textarea",s.content);null!==e&&e.focus()}.bind(this),200),s},setTitle:function(e,t){e=this._getDialogId(e);var i=b.get(e);if(void 0===i)throw new Error("Expected a valid dialog id, '"+e+"' does not match any active dialog.");var n=elByClass("dialogTitle",i.dialog);n.length&&(n[0].textContent=t)},setCallback:function(e,t,i){if("object"==typeof e){var n=y.get(e);void 0!==n&&(e=n.id)}var o=b.get(e);if(void 0===o)throw new Error("Expected a valid dialog id, '"+e+"' does not match any active dialog.");if(-1===A.indexOf(t))throw new Error("Invalid callback identifier, '"+t+"' is not recognized.");if("function"!=typeof i&&null!==i)throw new Error("Only functions or the 'null' value are acceptable callback values ('"+typeof i+"' given).");o[t]=i},_createDialog:function(e,t,i,n){var o=null;if(null===t&&null===(o=elById(e)))throw new Error("Expected either a HTML string or an existing element id.");var r=elCreate("div");r.classList.add("dialogContainer"),elAttr(r,"aria-hidden","true"),elAttr(r,"role","dialog"),elData(r,"id",e);var a=elCreate("header");r.appendChild(a);var s=l.getUniqueId();elAttr(r,"aria-labelledby",s);var c=elCreate("span");if(c.classList.add("dialogTitle"),c.textContent=i.title,elAttr(c,"id",s),a.appendChild(c),i.closable){var u=elCreate("a");u.className="dialogCloseButton jsTooltip",elAttr(u,"role","button"),elAttr(u,"tabindex","0"),elAttr(u,"title",i.closeButtonLabel),elAttr(u,"aria-label",i.closeButtonLabel),u.addEventListener(WCF_CLICK_EVENT,this._close.bind(this)),a.appendChild(u);var d=elCreate("span");d.className="icon icon24 fa-times",u.appendChild(d)}var h=elCreate("div");h.classList.add("dialogContent"),i.disableContentPadding&&h.classList.add("dialogContentNoPadding"),r.appendChild(h),h.addEventListener("wheel",function(e){for(var t,i,n,o=!1,r=e.target;;){if(t=r.clientHeight,i=r.scrollHeight,t<i){if(n=r.scrollTop,e.deltaY<0&&n>0){o=!0;break}if(e.deltaY>0&&n+t<i){o=!0;break}}if(!r||r===h)break;r=r.parentNode}!1===o&&e.preventDefault()},{passive:!1});var p;if(null===o)if("string"==typeof t)p=elCreate("div"),p.id=e,l.setInnerHtml(p,t);else{if(!(t instanceof DocumentFragment))throw new TypeError("'html' must either be a string or a DocumentFragment");for(var g,m=[],_=0,y=t.childNodes.length;_<y;_++)g=t.childNodes[_],g.nodeType===Node.ELEMENT_NODE&&m.push(g);"DIV"!==m[0].nodeName||m.length>1?(p=elCreate("div"),p.id=e,p.appendChild(t)):p=m[0]}else p=o;h.appendChild(p),"none"===p.style.getPropertyValue("display")&&elShow(p),b.set(e,{backdropCloseOnClick:i.backdropCloseOnClick,closable:i.closable,content:p,dialog:r,header:a,onBeforeClose:i.onBeforeClose,onClose:i.onClose,onShow:i.onShow,submitButton:null,inputFields:new f}),l.prepend(r,v),"function"==typeof i.onSetup&&i.onSetup(p),!0!==n&&this._updateDialog(e,null)},_updateDialog:function(e,t){var i=b.get(e);if(void 0===i)throw new Error("Expected a valid dialog id, '"+e+"' does not match any active dialog.");if("string"==typeof t&&l.setInnerHtml(i.content,t),"true"===elAttr(i.dialog,"aria-hidden")){null===m&&(m=this._maintainFocus.bind(this),document.body.addEventListener("focus",m,{capture:!0})),i.closable&&"true"===elAttr(v,"aria-hidden")&&window.addEventListener("keyup",E),elAttr(i.dialog,"aria-hidden","false"),elAttr(v,"aria-hidden","false"),elData(v,"close-on-click",i.backdropCloseOnClick?"true":"false"),g=e,C=document.activeElement;var n=elBySel(".dialogCloseButton",i.header);n&&elAttr(n,"inert",!0),this._setFocusToFirstItem(i.dialog),n&&n.removeAttribute("inert"),"function"==typeof i.onShow&&i.onShow(i.content),elDataBool(i.content,"is-static-dialog")&&h.fire("com.woltlab.wcf.dialog","openStatic",{content:i.content,id:e}),d.closeAll(),window.WCF.Dropdown.Interactive.Handler.closeAll()}this.rebuild(e),a.trigger()},_maintainFocus:function(e){if(g){var t=b.get(g);t.dialog.contains(e.target)||e.target.closest(".dropdownMenuContainer")||e.target.closest(".datePicker")||this._setFocusToFirstItem(t.dialog,!0)}},_setFocusToFirstItem:function(e,t){var i=this._getFirstFocusableChild(e);null!==i&&(t&&("username"!==i.id&&"username"!==i.name||"safari"===n.browser()&&"ios"===n.platform()&&(i=null)),i&&i.focus())},_getFirstFocusableChild:function(e){for(var t=elBySelAll(I.join(","),e),i=0,n=t.length;i<n;i++)if(t[i].offsetWidth&&t[i].offsetHeight&&t[i].getClientRects().length)return t[i];return null},rebuild:function(e){e=this._getDialogId(e);var t=b.get(e);if(void 0===t)throw new Error("Expected a valid dialog id, '"+e+"' does not match any active dialog.");if("true"!==elAttr(t.dialog,"aria-hidden")){var i=t.content.parentNode,o=elBySel(".formSubmit",t.content),r=0;null!==o?(i.classList.add("dialogForm"),o.classList.add("dialogFormSubmit"),r+=l.outerHeight(o),r-=1,i.style.setProperty("margin-bottom",r+"px","")):(i.classList.remove("dialogForm"),i.style.removeProperty("margin-bottom")),r+=l.outerHeight(t.header);var a=window.innerHeight*(_?1:.8)-r;if(i.style.setProperty("max-height",~~a+"px",""),"chrome"===n.browser()||"safari"===n.browser()){var s=parseFloat(window.getComputedStyle(t.content).width),c=Math.round(s)%2!=0;t.content.parentNode.classList[c?"add":"remove"]("jsWebKitFractionalPixel")}var u=w.get(e);if(void 0!==u&&"function"==typeof u._dialogSubmit){var d=elBySelAll('input[data-dialog-submit-on-enter="true"]',t.content),h=elBySel('.formSubmit > input[type="submit"], .formSubmit > button[data-type="submit"]',t.content);if(null===h)return void(0===d.length&&console.warn("Broken dialog, expected a submit button.",t.content));if(t.submitButton!==h){t.submitButton=h,h.addEventListener(WCF_CLICK_EVENT,function(t){t.preventDefault(),this._submit(e)}.bind(this));for(var f,g=null,m=0,v=d.length;m<v;m++)f=d[m],t.inputFields.has(f)||(-1!==S.indexOf(f.type)?(t.inputFields.add(f),null===g&&(g=function(t){p.Enter(t)&&(t.preventDefault(),this._submit(e))}.bind(this)),f.addEventListener("keydown",g)):console.warn("Unsupported input type.",f))}}}},_submit:function(e){var t=b.get(e),i=!0;t.inputFields.forEach(function(e){e.required&&(""===e.value.trim()?(elInnerError(e,o.get("wcf.global.form.error.empty")),i=!1):elInnerError(e,!1))}),i&&w.get(e)._dialogSubmit()},_close:function(e){e.preventDefault();var t=b.get(g);if("function"==typeof t.onBeforeClose)return t.onBeforeClose(g),!1;this.close(g)},_closeOnBackdrop:function(e){if(e.target!==v)return!0;"true"===elData(v,"close-on-click")?this._close(e):e.preventDefault()},close:function(e){e=this._getDialogId(e);var t=b.get(e);if(void 0===t)throw new Error("Expected a valid dialog id, '"+e+"' does not match any active dialog.");elAttr(t.dialog,"aria-hidden","true"),document.activeElement.closest(".dialogContainer")===t.dialog&&document.activeElement.blur(),"function"==typeof t.onClose&&t.onClose(e),g=null;for(var i=0;i<v.childElementCount;i++){var o=v.children[i];if("false"===elAttr(o,"aria-hidden")){g=elData(o,"id");break}}null===g?(elAttr(v,"aria-hidden","true"),elData(v,"close-on-click","false"),t.closable&&window.removeEventListener("keyup",E),u.pageOverlayClose()):(t=b.get(g),elData(v,"close-on-click",t.backdropCloseOnClick?"true":"false")),"desktop"!==n.platform()&&u.scrollEnable()},getDialog:function(e){return b.get(this._getDialogId(e))},isOpen:function(e){var t=this.getDialog(e);return void 0!==t&&"false"===elAttr(t.dialog,"aria-hidden")},destroy:function(e){if("object"!=typeof e||e instanceof String)throw new TypeError("Expected the callback object as parameter.");if(y.has(e)){var t=y.get(e).id;this.isOpen(t)&&this.close(t),b.delete(t),y.delete(e)}},_getDialogId:function(e){if("object"==typeof e){var t=y.get(e);if(void 0!==t)return t.id}return e.toString()},_ajaxSetup:function(){return{}}}}),define("WoltLabSuite/Core/Ajax/Status",["Language"],function(e){"use strict";var t=0,i=null,n=null;return{_init:function(){i=elCreate("div"),i.classList.add("spinner"),elAttr(i,"role","status");var t=elCreate("span");t.className="icon icon48 fa-spinner",i.appendChild(t);var n=elCreate("span");n.textContent=e.get("wcf.global.loading"),i.appendChild(n),document.body.appendChild(i)},show:function(){null===i&&this._init(),t++,null===n&&(n=window.setTimeout(function(){t&&i.classList.add("active"),n=null},250))},hide:function(){0===--t&&(null!==n&&window.clearTimeout(n),i.classList.remove("active"))}}}),define("WoltLabSuite/Core/Ajax/Request",["Core","Language","Dom/ChangeListener","Dom/Util","Ui/Dialog","WoltLabSuite/Core/Ajax/Status"],function(e,t,i,n,o,r){"use strict";function a(e){this._data=null,this._options={},this._previousXhr=null,this._xhr=null,this._init(e)}var s=!1,l=!1;return a.prototype={_init:function(t){this._options=e.extend({data:{},contentType:"application/x-www-form-urlencoded; charset=UTF-8",responseType:"application/json",type:"POST",url:"",withCredentials:!1,autoAbort:!1,ignoreError:!1,pinData:!1,silent:!1,includeRequestedWith:!0,failure:null,finalize:null,success:null,progress:null,uploadProgress:null,callbackObject:null},t),"object"==typeof t.callbackObject&&(this._options.callbackObject=t.callbackObject),this._options.url=e.convertLegacyUrl(this._options.url),0===this._options.url.indexOf("index.php")&&(this._options.url=WSC_API_URL+this._options.url),0===this._options.url.indexOf(WSC_API_URL)&&(this._options.includeRequestedWith=!0,this._options.withCredentials=!0),this._options.pinData&&(this._data=e.extend({},this._options.data)),null!==this._options.callbackObject&&("function"==typeof this._options.callbackObject._ajaxFailure&&(this._options.failure=this._options.callbackObject._ajaxFailure.bind(this._options.callbackObject)),"function"==typeof this._options.callbackObject._ajaxFinalize&&(this._options.finalize=this._options.callbackObject._ajaxFinalize.bind(this._options.callbackObject)),"function"==typeof this._options.callbackObject._ajaxSuccess&&(this._options.success=this._options.callbackObject._ajaxSuccess.bind(this._options.callbackObject)),"function"==typeof this._options.callbackObject._ajaxProgress&&(this._options.progress=this._options.callbackObject._ajaxProgress.bind(this._options.callbackObject)),"function"==typeof this._options.callbackObject._ajaxUploadProgress&&(this._options.uploadProgress=this._options.callbackObject._ajaxUploadProgress.bind(this._options.callbackObject))),!1===s&&(s=!0,window.addEventListener("beforeunload",function(){l=!0}))},sendRequest:function(t){(!0===t||this._options.autoAbort)&&this.abortPrevious(),this._options.silent||r.show(),this._xhr instanceof XMLHttpRequest&&(this._previousXhr=this._xhr),this._xhr=new XMLHttpRequest,this._xhr.open(this._options.type,this._options.url,!0),this._options.contentType&&this._xhr.setRequestHeader("Content-Type",this._options.contentType),(this._options.withCredentials||this._options.includeRequestedWith)&&this._xhr.setRequestHeader("X-Requested-With","XMLHttpRequest"),this._options.withCredentials&&(this._xhr.withCredentials=!0);var i=this,n=e.clone(this._options);if(this._xhr.onload=function(){this.readyState===XMLHttpRequest.DONE&&(this.status>=200&&this.status<300||304===this.status?n.responseType&&0!==this.getResponseHeader("Content-Type").indexOf(n.responseType)?i._failure(this,n):i._success(this,n):i._failure(this,n))},this._xhr.onerror=function(){i._failure(this,n)},this._options.progress&&(this._xhr.onprogress=this._options.progress),this._options.uploadProgress&&(this._xhr.upload.onprogress=this._options.uploadProgress),"POST"===this._options.type){var o=this._options.data;"object"==typeof o&&"FormData"!==e.getType(o)&&(o=e.serialize(o)),this._xhr.send(o)}else this._xhr.send()},abortPrevious:function(){null!==this._previousXhr&&(this._previousXhr.abort(),this._previousXhr=null,this._options.silent||r.hide())},setOption:function(e,t){this._options[e]=t},getOption:function(e){return objOwns(this._options,e)?this._options[e]:null},setData:function(t){null!==this._data&&"FormData"!==e.getType(t)&&(t=e.extend(this._data,t)),this._options.data=t},_success:function(e,t){if(t.silent||r.hide(),"function"==typeof t.success){var i=null;if("application/json"===e.getResponseHeader("Content-Type")){try{i=JSON.parse(e.responseText)}catch(i){return void this._failure(e,t)}i&&i.returnValues&&void 0!==i.returnValues.template&&(i.returnValues.template=i.returnValues.template.trim()),i&&i.forceBackgroundQueuePerform&&require(["WoltLabSuite/Core/BackgroundQueue"],function(e){e.invoke()})}t.success(i,e.responseText,e,t.data)}this._finalize(t)},_failure:function(e,i){if(!l){i.silent||r.hide();var a=null;try{a=JSON.parse(e.responseText)}catch(e){}var s=!0;if("function"==typeof i.failure&&(s=i.failure(a||{},e.responseText||"",e,i.data)),!0!==i.ignoreError&&!1!==s){var c=this.getErrorHtml(a,e);c&&(void 0===o&&(o=require("Ui/Dialog")),o.openStatic(n.getUniqueId(),c,{title:t.get("wcf.global.error.title")}))}this._finalize(i)}},getErrorHtml:function(e,t){var i="",n="";if(null!==e?(e.file&&e.line&&(i+="<br><p>File:</p><p>"+e.file+" in line "+e.line+"</p>"),e.stacktrace?i+="<br><p>Stacktrace:</p><p>"+e.stacktrace+"</p>":e.exceptionID&&(i+="<br><p>Exception ID: <code>"+e.exceptionID+"</code></p>"),n=e.message,e.previous.forEach(function(e){i+="<hr><p>"+e.message+"</p>",i+="<br><p>Stacktrace</p><p>"+e.stacktrace+"</p>"})):n=t.responseText,!n||"undefined"===n){if(!ENABLE_DEBUG_MODE&&!ENABLE_PRODUCTION_DEBUG_MODE)return null;n="XMLHttpRequest failed without a responseText. Check your browser console."}return'<div class="ajaxDebugMessage"><p>'+n+"</p>"+i+"</div>"},_finalize:function(e){"function"==typeof e.finalize&&e.finalize(this._xhr),this._previousXhr=null,i.trigger();for(var t=elBySelAll('a[href*="#"]'),n=0,o=t.length;n<o;n++){var r=t[n],a=elAttr(r,"href");-1===a.indexOf("AJAXProxy")&&-1===a.indexOf("ajax-proxy")||(a=a.substr(a.indexOf("#")),elAttr(r,"href",document.location.toString().replace(/#.*/,"")+a))}}},a}),define("WoltLabSuite/Core/Ajax",["AjaxRequest","Core","ObjectMap"],function(e,t,i){"use strict";var n=new i;return{api:function(t,i,o,r){void 0===e&&(e=require("AjaxRequest")),"object"!=typeof i&&(i={});var a=n.get(t);if(void 0===a){if("function"!=typeof t._ajaxSetup)throw new TypeError("Callback object must implement at least _ajaxSetup().");var s=t._ajaxSetup();s.pinData=!0,s.callbackObject=t,s.url||(s.url="index.php?ajax-proxy/&t="+SECURITY_TOKEN,s.withCredentials=!0),a=new e(s),n.set(t,a)}var l=null,c=null;return"function"==typeof o&&(l=a.getOption("success"),a.setOption("success",o)),"function"==typeof r&&(c=a.getOption("failure"),a.setOption("failure",r)),a.setData(i),a.sendRequest(),null!==l&&a.setOption("success",l),null!==c&&a.setOption("failure",c),a},apiOnce:function(t){void 0===e&&(e=require("AjaxRequest")),t.pinData=!1,t.callbackObject=null,t.url||(t.url="index.php?ajax-proxy/&t="+SECURITY_TOKEN,t.withCredentials=!0),new e(t).sendRequest(!1)},getRequestObject:function(e){if(!n.has(e))throw new Error("Expected a previously used callback object, provided object is unknown.");return n.get(e)}}}),define("WoltLabSuite/Core/BackgroundQueue",["Ajax"],function(e){"use strict";var t=0,i=!1,n="";return{setUrl:function(e){n=e},invoke:function(){if(""===n)return void console.error("The background queue has not been initialized yet.");i||(i=!0,e.api(this))},_ajaxSuccess:function(e){t++,e>0&&t<5?window.setTimeout(function(){i=!1,this.invoke()}.bind(this),1e3):(i=!1,t=0)},_ajaxSetup:function(){return{url:n,ignoreError:!0,silent:!0}}}}),function(){var e=function(e){"use strict";function t(e){if(e.paused||e.ended||m)return!1;try{u.clearRect(0,0,l,s),u.drawImage(e,0,0,l,s)}catch(e){}_=setTimeout(function(){t(e)},M.duration),B.setIcon(c)}function i(e){var t=/^#?([a-f\d])([a-f\d])([a-f\d])$/i;e=e.replace(t,function(e,t,i,n){return t+t+i+i+n+n});var i=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e);return!!i&&{r:parseInt(i[1],16),g:parseInt(i[2],16),b:parseInt(i[3],16)}}function n(e,t){var i,n={};for(i in e)n[i]=e[i];for(i in t)n[i]=t[i];return n}function o(){return y.hidden||y.msHidden||y.webkitHidden||y.mozHidden}e=e||{};var r,a,s,l,c,u,d,h,f,p,g,m,v,b,_,y,w={bgColor:"#d00",textColor:"#fff",fontFamily:"sans-serif",fontStyle:"bold",type:"circle",position:"down",animation:"slide",elementId:!1,element:null,dataUrl:!1,win:window};v={},v.ff="undefined"!=typeof InstallTrigger,v.chrome=!!window.chrome,v.opera=!!window.opera||navigator.userAgent.indexOf("Opera")>=0,v.ie=!1,v.safari=Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor")>0,v.supported=v.chrome||v.ff||v.opera;var C=[];g=function(){},h=m=!1;var E={};E.ready=function(){h=!0,E.reset(),g()},E.reset=function(){h&&(C=[],f=!1,p=!1,u.clearRect(0,0,l,s),u.drawImage(d,0,0,l,s),B.setIcon(c),window.clearTimeout(b),window.clearTimeout(_))},E.start=function(){if(h&&!p){var e=function(){f=C[0],p=!1,C.length>0&&(C.shift(),E.start())};if(C.length>0){p=!0;var t=function(){["type","animation","bgColor","textColor","fontFamily","fontStyle"].forEach(function(e){e in C[0].options&&(r[e]=C[0].options[e])}),M.run(C[0].options,function(){e()},!1)};f?M.run(f.options,function(){t()},!0):t()}}};var L={},A=function(e){return e.n="number"==typeof e.n?Math.abs(0|e.n):e.n,e.x=l*e.x,e.y=s*e.y,e.w=l*e.w,e.h=s*e.h,e.len=(""+e.n).length,e};L.circle=function(e){e=A(e);var t=!1;2===e.len?(e.x=e.x-.4*e.w,e.w=1.4*e.w,t=!0):e.len>=3&&(e.x=e.x-.65*e.w,e.w=1.65*e.w,t=!0),u.clearRect(0,0,l,s),u.drawImage(d,0,0,l,s),u.beginPath(),u.font=r.fontStyle+" "+Math.floor(e.h*(e.n>99?.85:1))+"px "+r.fontFamily,u.textAlign="center",t?(u.moveTo(e.x+e.w/2,e.y),u.lineTo(e.x+e.w-e.h/2,e.y),u.quadraticCurveTo(e.x+e.w,e.y,e.x+e.w,e.y+e.h/2),u.lineTo(e.x+e.w,e.y+e.h-e.h/2),u.quadraticCurveTo(e.x+e.w,e.y+e.h,e.x+e.w-e.h/2,e.y+e.h),u.lineTo(e.x+e.h/2,e.y+e.h),u.quadraticCurveTo(e.x,e.y+e.h,e.x,e.y+e.h-e.h/2),u.lineTo(e.x,e.y+e.h/2),u.quadraticCurveTo(e.x,e.y,e.x+e.h/2,e.y)):u.arc(e.x+e.w/2,e.y+e.h/2,e.h/2,0,2*Math.PI),u.fillStyle="rgba("+r.bgColor.r+","+r.bgColor.g+","+r.bgColor.b+","+e.o+")",u.fill(),u.closePath(),u.beginPath(),u.stroke(),u.fillStyle="rgba("+r.textColor.r+","+r.textColor.g+","+r.textColor.b+","+e.o+")","number"==typeof e.n&&e.n>999?u.fillText((e.n>9999?9:Math.floor(e.n/1e3))+"k+",Math.floor(e.x+e.w/2),Math.floor(e.y+e.h-.2*e.h)):u.fillText(e.n,Math.floor(e.x+e.w/2),Math.floor(e.y+e.h-.15*e.h)),u.closePath()},L.rectangle=function(e){e=A(e);2===e.len?(e.x=e.x-.4*e.w,e.w=1.4*e.w):e.len>=3&&(e.x=e.x-.65*e.w,e.w=1.65*e.w),u.clearRect(0,0,l,s),u.drawImage(d,0,0,l,s),u.beginPath(),u.font=r.fontStyle+" "+Math.floor(e.h*(e.n>99?.9:1))+"px "+r.fontFamily,u.textAlign="center",u.fillStyle="rgba("+r.bgColor.r+","+r.bgColor.g+","+r.bgColor.b+","+e.o+")",u.fillRect(e.x,e.y,e.w,e.h),u.fillStyle="rgba("+r.textColor.r+","+r.textColor.g+","+r.textColor.b+","+e.o+")","number"==typeof e.n&&e.n>999?u.fillText((e.n>9999?9:Math.floor(e.n/1e3))+"k+",Math.floor(e.x+e.w/2),Math.floor(e.y+e.h-.2*e.h)):u.fillText(e.n,Math.floor(e.x+e.w/2),Math.floor(e.y+e.h-.15*e.h)),u.closePath()};var S=function(e,t){t=("string"==typeof t?{animation:t}:t)||{},g=function(){try{if("number"==typeof e?e>0:""!==e){var n={type:"badge",options:{n:e}};if("animation"in t&&M.types[""+t.animation]&&(n.options.animation=""+t.animation),"type"in t&&L[""+t.type]&&(n.options.type=""+t.type),["bgColor","textColor"].forEach(function(e){e in t&&(n.options[e]=i(t[e]))}),["fontStyle","fontFamily"].forEach(function(e){e in t&&(n.options[e]=t[e])}),C.push(n),C.length>100)throw new Error("Too many badges requests in queue.");E.start()}else E.reset()}catch(e){throw new Error("Error setting badge. Message: "+e.message)}},h&&g()},I=function(e){g=function(){try{var t=e.width,i=e.height,n=document.createElement("img"),o=t/l<i/s?t/l:i/s;n.setAttribute("crossOrigin","anonymous"),n.onload=function(){u.clearRect(0,0,l,s),u.drawImage(n,0,0,l,s),B.setIcon(c)},n.setAttribute("src",e.getAttribute("src")),n.height=i/o,n.width=t/o}catch(e){throw new Error("Error setting image. Message: "+e.message)}},h&&g()},x=function(e){g=function(){B.setIconSrc(e)},h&&g()},D=function(e){g=function(){try{if("stop"===e)return m=!0,E.reset(),void(m=!1);e.addEventListener("play",function(){t(this)},!1)}catch(e){throw new Error("Error setting video. Message: "+e.message)}},h&&g()},k=function(e){if(window.URL&&window.URL.createObjectURL||(window.URL=window.URL||{},window.URL.createObjectURL=function(e){return e}),v.supported){var i=!1;navigator.getUserMedia=navigator.getUserMedia||navigator.oGetUserMedia||navigator.msGetUserMedia||navigator.mozGetUserMedia||navigator.webkitGetUserMedia,g=function(){try{if("stop"===e)return m=!0,E.reset(),void(m=!1);i=document.createElement("video"),i.width=l,i.height=s,navigator.getUserMedia({video:!0,audio:!1},function(e){i.src=URL.createObjectURL(e),i.play(),t(i)},function(){})}catch(e){throw new Error("Error setting webcam. Message: "+e.message)}},h&&g()}},T=function(e,t){var n=e;null==t&&"[object Object]"==Object.prototype.toString.call(e)||(n={},n[e]=t);for(var o=Object.keys(n),a=0;a<o.length;a++)"bgColor"==o[a]||"textColor"==o[a]?r[o[a]]=i(n[o[a]]):r[o[a]]=n[o[a]];C.push(f),E.start()},B={};B.getIcons=function(){var e=[];return r.element?e=[r.element]:r.elementId?(e=[y.getElementById(r.elementId)],e[0].setAttribute("href",e[0].getAttribute("src"))):(e=function(){for(var e=[],t=y.getElementsByTagName("head")[0].getElementsByTagName("link"),i=0;i<t.length;i++)/(^|\s)icon(\s|$)/i.test(t[i].getAttribute("rel"))&&e.push(t[i]);return e}(),0===e.length&&(e=[y.createElement("link")],e[0].setAttribute("rel","icon"),y.getElementsByTagName("head")[0].appendChild(e[0]))),e.forEach(function(e){e.setAttribute("type","image/png")}),e},B.setIcon=function(e){var t=e.toDataURL("image/png");B.setIconSrc(t)},B.setIconSrc=function(e){if(r.dataUrl&&r.dataUrl(e),r.element)r.element.setAttribute("href",e),r.element.setAttribute("src",e);else if(r.elementId){var t=y.getElementById(r.elementId);t.setAttribute("href",e),t.setAttribute("src",e)}else if(v.ff||v.opera){var i=a[a.length-1],n=y.createElement("link");a=[n],v.opera&&n.setAttribute("rel","icon"),n.setAttribute("rel","icon"),n.setAttribute("type","image/png"),y.getElementsByTagName("head")[0].appendChild(n),n.setAttribute("href",e),i.parentNode&&i.parentNode.removeChild(i)}else a.forEach(function(t){t.setAttribute("href",e)})};var M={};return M.duration=40,M.types={},M.types.fade=[{x:.4,y:.4,w:.6,h:.6,o:0},{x:.4,y:.4,w:.6,h:.6,o:.1},{x:.4,y:.4,w:.6,h:.6,o:.2},{x:.4,y:.4,w:.6,h:.6,o:.3},{x:.4,y:.4,w:.6,h:.6,o:.4},{x:.4,y:.4,w:.6,h:.6,o:.5},{x:.4,y:.4,w:.6,h:.6,o:.6},{x:.4,y:.4,w:.6,h:.6,o:.7},{x:.4,y:.4,w:.6,h:.6,o:.8},{x:.4,y:.4,w:.6,h:.6,o:.9},{x:.4,y:.4,w:.6,h:.6,o:1}],M.types.none=[{x:.4,y:.4,w:.6,h:.6,o:1}],M.types.pop=[{x:1,y:1,w:0,h:0,o:1},{x:.9,y:.9,w:.1,h:.1,o:1},{x:.8,y:.8,w:.2,h:.2,o:1},{x:.7,y:.7,w:.3,h:.3,o:1},{x:.6,y:.6,w:.4,h:.4,o:1},{x:.5,y:.5,w:.5,h:.5,o:1},{x:.4,y:.4,w:.6,h:.6,o:1}],M.types.popFade=[{x:.75,y:.75,w:0,h:0,o:0},{x:.65,y:.65,w:.1,h:.1,o:.2},{x:.6,y:.6,w:.2,h:.2,o:.4},{x:.55,y:.55,w:.3,h:.3,o:.6},{x:.5,y:.5,w:.4,h:.4,o:.8},{x:.45,y:.45,w:.5,h:.5,o:.9},{x:.4,y:.4,w:.6,h:.6,o:1}],M.types.slide=[{x:.4,y:1,w:.6,h:.6,o:1},{x:.4,y:.9,w:.6,h:.6,o:1},{x:.4,y:.9,w:.6,h:.6,o:1},{x:.4,y:.8,w:.6,h:.6,o:1},{x:.4,y:.7,w:.6,h:.6,o:1},{x:.4,y:.6,w:.6,h:.6,o:1},{x:.4,y:.5,w:.6,h:.6,o:1},{x:.4,y:.4,w:.6,h:.6,o:1}],M.run=function(e,t,i,a){var s=M.types[o()?"none":r.animation];if(a=!0===i?void 0!==a?a:s.length-1:void 0!==a?a:0,t=t||function(){},!(a<s.length&&a>=0))return void t();L[r.type](n(e,s[a])),b=setTimeout(function(){i?a-=1:a+=1,M.run(e,t,i,a)},M.duration),B.setIcon(c)},function(){r=n(w,e),r.bgColor=i(r.bgColor),r.textColor=i(r.textColor),r.position=r.position.toLowerCase(),r.animation=M.types[""+r.animation]?r.animation:w.animation,y=r.win.document;var t=r.position.indexOf("up")>-1,o=r.position.indexOf("left")>-1;if(t||o)for(var h in M.types)for(var f=0;f<M.types[h].length;f++){var p=M.types[h][f];t&&(p.y<.6?p.y=p.y-.4:p.y=p.y-2*p.y+(1-p.w)),o&&(p.x<.6?p.x=p.x-.4:p.x=p.x-2*p.x+(1-p.h)),M.types[h][f]=p}r.type=L[""+r.type]?r.type:w.type,a=B.getIcons(),c=document.createElement("canvas"),d=document.createElement("img");var g=a[a.length-1];g.hasAttribute("href")?(d.setAttribute("crossOrigin","anonymous"),d.onload=function(){s=d.height>0?d.height:32,l=d.width>0?d.width:32,c.height=s,c.width=l,u=c.getContext("2d"),E.ready()},d.setAttribute("src",g.getAttribute("href"))):(s=32,l=32,d.height=s,d.width=l,c.height=s,c.width=l,u=c.getContext("2d"),E.ready())}(),{badge:S,video:D,image:I,rawImageSrc:x,webcam:k,setOpt:T,reset:E.reset,browser:{supported:v.supported}}};void 0!==define&&define.amd?define("favico",[],function(){return e}):"undefined"!=typeof module&&module.exports?module.exports=e:this.Favico=e}(),function(e,t,i){var n=window.matchMedia;"undefined"!=typeof module&&module.exports?module.exports=i(n):"function"==typeof define&&define.amd?define("enquire",[],function(){return t.enquire=i(n)}):t.enquire=i(n)}(0,this,function(e){"use strict";function t(e,t){var i=0,n=e.length;for(i;i<n&&!1!==t(e[i],i);i++);}function i(e){return"[object Array]"===Object.prototype.toString.apply(e)}function n(e){return"function"==typeof e}function o(e){this.options=e,!e.deferSetup&&this.setup()}function r(t,i){this.query=t,this.isUnconditional=i,this.handlers=[],this.mql=e(t);var n=this;this.listener=function(e){n.mql=e,n.assess()},this.mql.addListener(this.listener)}function a(){if(!e)throw new Error("matchMedia not present, legacy browsers require a polyfill");this.queries={},this.browserIsIncapable=!e("only all").matches}return o.prototype={setup:function(){this.options.setup&&this.options.setup(),this.initialised=!0},on:function(){!this.initialised&&this.setup(),this.options.match&&this.options.match()},off:function(){this.options.unmatch&&this.options.unmatch()},destroy:function(){this.options.destroy?this.options.destroy():this.off()},equals:function(e){return this.options===e||this.options.match===e}},r.prototype={addHandler:function(e){var t=new o(e);this.handlers.push(t),this.matches()&&t.on()},removeHandler:function(e){var i=this.handlers;t(i,function(t,n){if(t.equals(e))return t.destroy(),!i.splice(n,1)})},matches:function(){return this.mql.matches||this.isUnconditional},clear:function(){t(this.handlers,function(e){e.destroy()}),this.mql.removeListener(this.listener),this.handlers.length=0},assess:function(){var e=this.matches()?"on":"off";t(this.handlers,function(t){t[e]()})}},a.prototype={register:function(e,o,a){var s=this.queries,l=a&&this.browserIsIncapable;return s[e]||(s[e]=new r(e,l)),n(o)&&(o={match:o}),i(o)||(o=[o]),t(o,function(t){n(t)&&(t={match:t}),s[e].addHandler(t)}),this},unregister:function(e,t){var i=this.queries[e];return i&&(t?i.removeHandler(t):(i.clear(),delete this.queries[e])),this}},new a}),function e(t,i,n){function o(a,s){if(!i[a]){if(!t[a]){var l="function"==typeof require&&require;if(!s&&l)return l(a,!0);if(r)return r(a,!0);var c=new Error("Cannot find module '"+a+"'");throw c.code="MODULE_NOT_FOUND",c}var u=i[a]={exports:{}};t[a][0].call(u.exports,function(e){var i=t[a][1][e];return o(i||e)},u,u.exports,e,t,i,n)}return i[a].exports}for(var r="function"==typeof require&&require,a=0;a<n.length;a++)o(n[a]);return o}({1:[function(e,t,i){"use strict";var n=e("../main");"function"==typeof define&&define.amd?define("perfect-scrollbar",n):(window.PerfectScrollbar=n,void 0===window.Ps&&(window.Ps=n))},{"../main":7}],2:[function(e,t,i){"use strict";function n(e,t){var i=e.className.split(" ");i.indexOf(t)<0&&i.push(t),e.className=i.join(" ")}function o(e,t){var i=e.className.split(" "),n=i.indexOf(t);n>=0&&i.splice(n,1),e.className=i.join(" ")}i.add=function(e,t){e.classList?e.classList.add(t):n(e,t)},i.remove=function(e,t){e.classList?e.classList.remove(t):o(e,t)},i.list=function(e){
+return e.classList?Array.prototype.slice.apply(e.classList):e.className.split(" ")}},{}],3:[function(e,t,i){"use strict";function n(e,t){return window.getComputedStyle(e)[t]}function o(e,t,i){return"number"==typeof i&&(i=i.toString()+"px"),e.style[t]=i,e}function r(e,t){for(var i in t){var n=t[i];"number"==typeof n&&(n=n.toString()+"px"),e.style[i]=n}return e}var a={};a.e=function(e,t){var i=document.createElement(e);return i.className=t,i},a.appendTo=function(e,t){return t.appendChild(e),e},a.css=function(e,t,i){return"object"==typeof t?r(e,t):void 0===i?n(e,t):o(e,t,i)},a.matches=function(e,t){return void 0!==e.matches?e.matches(t):void 0!==e.matchesSelector?e.matchesSelector(t):void 0!==e.webkitMatchesSelector?e.webkitMatchesSelector(t):void 0!==e.mozMatchesSelector?e.mozMatchesSelector(t):void 0!==e.msMatchesSelector?e.msMatchesSelector(t):void 0},a.remove=function(e){void 0!==e.remove?e.remove():e.parentNode&&e.parentNode.removeChild(e)},a.queryChildren=function(e,t){return Array.prototype.filter.call(e.childNodes,function(e){return a.matches(e,t)})},t.exports=a},{}],4:[function(e,t,i){"use strict";var n=function(e){this.element=e,this.events={}};n.prototype.bind=function(e,t){void 0===this.events[e]&&(this.events[e]=[]),this.events[e].push(t),this.element.addEventListener(e,t,!1)},n.prototype.unbind=function(e,t){var i=void 0!==t;this.events[e]=this.events[e].filter(function(n){return!(!i||n===t)||(this.element.removeEventListener(e,n,!1),!1)},this)},n.prototype.unbindAll=function(){for(var e in this.events)this.unbind(e)};var o=function(){this.eventElements=[]};o.prototype.eventElement=function(e){var t=this.eventElements.filter(function(t){return t.element===e})[0];return void 0===t&&(t=new n(e),this.eventElements.push(t)),t},o.prototype.bind=function(e,t,i){this.eventElement(e).bind(t,i)},o.prototype.unbind=function(e,t,i){this.eventElement(e).unbind(t,i)},o.prototype.unbindAll=function(){for(var e=0;e<this.eventElements.length;e++)this.eventElements[e].unbindAll()},o.prototype.once=function(e,t,i){var n=this.eventElement(e),o=function(e){n.unbind(t,o),i(e)};n.bind(t,o)},t.exports=o},{}],5:[function(e,t,i){"use strict";t.exports=function(){function e(){return Math.floor(65536*(1+Math.random())).toString(16).substring(1)}return function(){return e()+e()+"-"+e()+"-"+e()+"-"+e()+"-"+e()+e()+e()}}()},{}],6:[function(e,t,i){"use strict";var n=e("./class"),o=e("./dom"),r=i.toInt=function(e){return parseInt(e,10)||0},a=i.clone=function(e){if(e){if(e.constructor===Array)return e.map(a);if("object"==typeof e){var t={};for(var i in e)t[i]=a(e[i]);return t}return e}return null};i.extend=function(e,t){var i=a(e);for(var n in t)i[n]=a(t[n]);return i},i.isEditable=function(e){return o.matches(e,"input,[contenteditable]")||o.matches(e,"select,[contenteditable]")||o.matches(e,"textarea,[contenteditable]")||o.matches(e,"button,[contenteditable]")},i.removePsClasses=function(e){for(var t=n.list(e),i=0;i<t.length;i++){var o=t[i];0===o.indexOf("ps-")&&n.remove(e,o)}},i.outerWidth=function(e){return r(o.css(e,"width"))+r(o.css(e,"paddingLeft"))+r(o.css(e,"paddingRight"))+r(o.css(e,"borderLeftWidth"))+r(o.css(e,"borderRightWidth"))},i.startScrolling=function(e,t){n.add(e,"ps-in-scrolling"),void 0!==t?n.add(e,"ps-"+t):(n.add(e,"ps-x"),n.add(e,"ps-y"))},i.stopScrolling=function(e,t){n.remove(e,"ps-in-scrolling"),void 0!==t?n.remove(e,"ps-"+t):(n.remove(e,"ps-x"),n.remove(e,"ps-y"))},i.env={isWebKit:"WebkitAppearance"in document.documentElement.style,supportsTouch:"ontouchstart"in window||window.DocumentTouch&&document instanceof window.DocumentTouch,supportsIePointer:null!==window.navigator.msMaxTouchPoints}},{"./class":2,"./dom":3}],7:[function(e,t,i){"use strict";var n=e("./plugin/destroy"),o=e("./plugin/initialize"),r=e("./plugin/update");t.exports={initialize:o,update:r,destroy:n}},{"./plugin/destroy":9,"./plugin/initialize":17,"./plugin/update":21}],8:[function(e,t,i){"use strict";t.exports={handlers:["click-rail","drag-scrollbar","keyboard","wheel","touch"],maxScrollbarLength:null,minScrollbarLength:null,scrollXMarginOffset:0,scrollYMarginOffset:0,suppressScrollX:!1,suppressScrollY:!1,swipePropagation:!0,useBothWheelAxes:!1,wheelPropagation:!1,wheelSpeed:1,theme:"default"}},{}],9:[function(e,t,i){"use strict";var n=e("../lib/helper"),o=e("../lib/dom"),r=e("./instances");t.exports=function(e){var t=r.get(e);t&&(t.event.unbindAll(),o.remove(t.scrollbarX),o.remove(t.scrollbarY),o.remove(t.scrollbarXRail),o.remove(t.scrollbarYRail),n.removePsClasses(e),r.remove(e))}},{"../lib/dom":3,"../lib/helper":6,"./instances":18}],10:[function(e,t,i){"use strict";function n(e,t){function i(e){return e.getBoundingClientRect()}var n=function(e){e.stopPropagation()};t.event.bind(t.scrollbarY,"click",n),t.event.bind(t.scrollbarYRail,"click",function(n){var o=n.pageY-window.pageYOffset-i(t.scrollbarYRail).top,s=o>t.scrollbarYTop?1:-1;a(e,"top",e.scrollTop+s*t.containerHeight),r(e),n.stopPropagation()}),t.event.bind(t.scrollbarX,"click",n),t.event.bind(t.scrollbarXRail,"click",function(n){var o=n.pageX-window.pageXOffset-i(t.scrollbarXRail).left,s=o>t.scrollbarXLeft?1:-1;a(e,"left",e.scrollLeft+s*t.containerWidth),r(e),n.stopPropagation()})}var o=e("../instances"),r=e("../update-geometry"),a=e("../update-scroll");t.exports=function(e){n(e,o.get(e))}},{"../instances":18,"../update-geometry":19,"../update-scroll":20}],11:[function(e,t,i){"use strict";function n(e,t){function i(i){var o=n+i*t.railXRatio,a=Math.max(0,t.scrollbarXRail.getBoundingClientRect().left)+t.railXRatio*(t.railXWidth-t.scrollbarXWidth);t.scrollbarXLeft=o<0?0:o>a?a:o;var s=r.toInt(t.scrollbarXLeft*(t.contentWidth-t.containerWidth)/(t.containerWidth-t.railXRatio*t.scrollbarXWidth))-t.negativeScrollAdjustment;c(e,"left",s)}var n=null,o=null,s=function(t){i(t.pageX-o),l(e),t.stopPropagation(),t.preventDefault()},u=function(){r.stopScrolling(e,"x"),t.event.unbind(t.ownerDocument,"mousemove",s)};t.event.bind(t.scrollbarX,"mousedown",function(i){o=i.pageX,n=r.toInt(a.css(t.scrollbarX,"left"))*t.railXRatio,r.startScrolling(e,"x"),t.event.bind(t.ownerDocument,"mousemove",s),t.event.once(t.ownerDocument,"mouseup",u),i.stopPropagation(),i.preventDefault()})}function o(e,t){function i(i){var o=n+i*t.railYRatio,a=Math.max(0,t.scrollbarYRail.getBoundingClientRect().top)+t.railYRatio*(t.railYHeight-t.scrollbarYHeight);t.scrollbarYTop=o<0?0:o>a?a:o;var s=r.toInt(t.scrollbarYTop*(t.contentHeight-t.containerHeight)/(t.containerHeight-t.railYRatio*t.scrollbarYHeight));c(e,"top",s)}var n=null,o=null,s=function(t){i(t.pageY-o),l(e),t.stopPropagation(),t.preventDefault()},u=function(){r.stopScrolling(e,"y"),t.event.unbind(t.ownerDocument,"mousemove",s)};t.event.bind(t.scrollbarY,"mousedown",function(i){o=i.pageY,n=r.toInt(a.css(t.scrollbarY,"top"))*t.railYRatio,r.startScrolling(e,"y"),t.event.bind(t.ownerDocument,"mousemove",s),t.event.once(t.ownerDocument,"mouseup",u),i.stopPropagation(),i.preventDefault()})}var r=e("../../lib/helper"),a=e("../../lib/dom"),s=e("../instances"),l=e("../update-geometry"),c=e("../update-scroll");t.exports=function(e){var t=s.get(e);n(e,t),o(e,t)}},{"../../lib/dom":3,"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],12:[function(e,t,i){"use strict";function n(e,t){function i(i,n){var o=e.scrollTop;if(0===i){if(!t.scrollbarYActive)return!1;if(0===o&&n>0||o>=t.contentHeight-t.containerHeight&&n<0)return!t.settings.wheelPropagation}var r=e.scrollLeft;if(0===n){if(!t.scrollbarXActive)return!1;if(0===r&&i<0||r>=t.contentWidth-t.containerWidth&&i>0)return!t.settings.wheelPropagation}return!0}var n=!1;t.event.bind(e,"mouseenter",function(){n=!0}),t.event.bind(e,"mouseleave",function(){n=!1});var a=!1;t.event.bind(t.ownerDocument,"keydown",function(c){if(!(c.isDefaultPrevented&&c.isDefaultPrevented()||c.defaultPrevented)){var u=r.matches(t.scrollbarX,":focus")||r.matches(t.scrollbarY,":focus");if(n||u){var d=document.activeElement?document.activeElement:t.ownerDocument.activeElement;if(d){if("IFRAME"===d.tagName)d=d.contentDocument.activeElement;else for(;d.shadowRoot;)d=d.shadowRoot.activeElement;if(o.isEditable(d))return}var h=0,f=0;switch(c.which){case 37:h=c.metaKey?-t.contentWidth:c.altKey?-t.containerWidth:-30;break;case 38:f=c.metaKey?t.contentHeight:c.altKey?t.containerHeight:30;break;case 39:h=c.metaKey?t.contentWidth:c.altKey?t.containerWidth:30;break;case 40:f=c.metaKey?-t.contentHeight:c.altKey?-t.containerHeight:-30;break;case 33:f=90;break;case 32:f=c.shiftKey?90:-90;break;case 34:f=-90;break;case 35:f=c.ctrlKey?-t.contentHeight:-t.containerHeight;break;case 36:f=c.ctrlKey?e.scrollTop:t.containerHeight;break;default:return}l(e,"top",e.scrollTop-f),l(e,"left",e.scrollLeft+h),s(e),a=i(h,f),a&&c.preventDefault()}}})}var o=e("../../lib/helper"),r=e("../../lib/dom"),a=e("../instances"),s=e("../update-geometry"),l=e("../update-scroll");t.exports=function(e){n(e,a.get(e))}},{"../../lib/dom":3,"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],13:[function(e,t,i){"use strict";function n(e,t){function i(i,n){var o=e.scrollTop;if(0===i){if(!t.scrollbarYActive)return!1;if(0===o&&n>0||o>=t.contentHeight-t.containerHeight&&n<0)return!t.settings.wheelPropagation}var r=e.scrollLeft;if(0===n){if(!t.scrollbarXActive)return!1;if(0===r&&i<0||r>=t.contentWidth-t.containerWidth&&i>0)return!t.settings.wheelPropagation}return!0}function n(e){var t=e.deltaX,i=-1*e.deltaY;return void 0!==t&&void 0!==i||(t=-1*e.wheelDeltaX/6,i=e.wheelDeltaY/6),e.deltaMode&&1===e.deltaMode&&(t*=10,i*=10),t!==t&&i!==i&&(t=0,i=e.wheelDelta),e.shiftKey?[-i,-t]:[t,i]}function o(t,i){var n=e.querySelector("textarea:hover, select[multiple]:hover, .ps-child:hover");if(n){if(!window.getComputedStyle(n).overflow.match(/(scroll|auto)/))return!1;var o=n.scrollHeight-n.clientHeight;if(o>0&&!(0===n.scrollTop&&i>0||n.scrollTop===o&&i<0))return!0;var r=n.scrollLeft-n.clientWidth;if(r>0&&!(0===n.scrollLeft&&t<0||n.scrollLeft===r&&t>0))return!0}return!1}function s(s){var c=n(s),u=c[0],d=c[1];o(u,d)||(l=!1,t.settings.useBothWheelAxes?t.scrollbarYActive&&!t.scrollbarXActive?(d?a(e,"top",e.scrollTop-d*t.settings.wheelSpeed):a(e,"top",e.scrollTop+u*t.settings.wheelSpeed),l=!0):t.scrollbarXActive&&!t.scrollbarYActive&&(u?a(e,"left",e.scrollLeft+u*t.settings.wheelSpeed):a(e,"left",e.scrollLeft-d*t.settings.wheelSpeed),l=!0):(a(e,"top",e.scrollTop-d*t.settings.wheelSpeed),a(e,"left",e.scrollLeft+u*t.settings.wheelSpeed)),r(e),(l=l||i(u,d))&&(s.stopPropagation(),s.preventDefault()))}var l=!1;void 0!==window.onwheel?t.event.bind(e,"wheel",s):void 0!==window.onmousewheel&&t.event.bind(e,"mousewheel",s)}var o=e("../instances"),r=e("../update-geometry"),a=e("../update-scroll");t.exports=function(e){n(e,o.get(e))}},{"../instances":18,"../update-geometry":19,"../update-scroll":20}],14:[function(e,t,i){"use strict";function n(e,t){t.event.bind(e,"scroll",function(){r(e)})}var o=e("../instances"),r=e("../update-geometry");t.exports=function(e){n(e,o.get(e))}},{"../instances":18,"../update-geometry":19}],15:[function(e,t,i){"use strict";function n(e,t){function i(){var e=window.getSelection?window.getSelection():document.getSelection?document.getSelection():"";return 0===e.toString().length?null:e.getRangeAt(0).commonAncestorContainer}function n(){c||(c=setInterval(function(){if(!r.get(e))return void clearInterval(c);s(e,"top",e.scrollTop+u.top),s(e,"left",e.scrollLeft+u.left),a(e)},50))}function l(){c&&(clearInterval(c),c=null),o.stopScrolling(e)}var c=null,u={top:0,left:0},d=!1;t.event.bind(t.ownerDocument,"selectionchange",function(){e.contains(i())?d=!0:(d=!1,l())}),t.event.bind(window,"mouseup",function(){d&&(d=!1,l())}),t.event.bind(window,"keyup",function(){d&&(d=!1,l())}),t.event.bind(window,"mousemove",function(t){if(d){var i={x:t.pageX,y:t.pageY},r={left:e.offsetLeft,right:e.offsetLeft+e.offsetWidth,top:e.offsetTop,bottom:e.offsetTop+e.offsetHeight};i.x<r.left+3?(u.left=-5,o.startScrolling(e,"x")):i.x>r.right-3?(u.left=5,o.startScrolling(e,"x")):u.left=0,i.y<r.top+3?(u.top=r.top+3-i.y<5?-5:-20,o.startScrolling(e,"y")):i.y>r.bottom-3?(u.top=i.y-r.bottom+3<5?5:20,o.startScrolling(e,"y")):u.top=0,0===u.top&&0===u.left?l():n()}})}var o=e("../../lib/helper"),r=e("../instances"),a=e("../update-geometry"),s=e("../update-scroll");t.exports=function(e){n(e,r.get(e))}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],16:[function(e,t,i){"use strict";function n(e,t,i,n){function o(i,n){var o=e.scrollTop,r=e.scrollLeft,a=Math.abs(i),s=Math.abs(n);if(s>a){if(n<0&&o===t.contentHeight-t.containerHeight||n>0&&0===o)return!t.settings.swipePropagation}else if(a>s&&(i<0&&r===t.contentWidth-t.containerWidth||i>0&&0===r))return!t.settings.swipePropagation;return!0}function l(t,i){s(e,"top",e.scrollTop-i),s(e,"left",e.scrollLeft-t),a(e)}function c(){y=!0}function u(){y=!1}function d(e){return e.targetTouches?e.targetTouches[0]:e}function h(e){return!(!e.targetTouches||1!==e.targetTouches.length)||!(!e.pointerType||"mouse"===e.pointerType||e.pointerType===e.MSPOINTER_TYPE_MOUSE)}function f(e){if(h(e)){w=!0;var t=d(e);m.pageX=t.pageX,m.pageY=t.pageY,v=(new Date).getTime(),null!==_&&clearInterval(_),e.stopPropagation()}}function p(e){if(!w&&t.settings.swipePropagation&&f(e),!y&&w&&h(e)){var i=d(e),n={pageX:i.pageX,pageY:i.pageY},r=n.pageX-m.pageX,a=n.pageY-m.pageY;l(r,a),m=n;var s=(new Date).getTime(),c=s-v;c>0&&(b.x=r/c,b.y=a/c,v=s),o(r,a)&&(e.stopPropagation(),e.preventDefault())}}function g(){!y&&w&&(w=!1,clearInterval(_),_=setInterval(function(){return r.get(e)&&(b.x||b.y)?Math.abs(b.x)<.01&&Math.abs(b.y)<.01?void clearInterval(_):(l(30*b.x,30*b.y),b.x*=.8,void(b.y*=.8)):void clearInterval(_)},10))}var m={},v=0,b={},_=null,y=!1,w=!1;i?(t.event.bind(window,"touchstart",c),t.event.bind(window,"touchend",u),t.event.bind(e,"touchstart",f),t.event.bind(e,"touchmove",p),t.event.bind(e,"touchend",g)):n&&(window.PointerEvent?(t.event.bind(window,"pointerdown",c),t.event.bind(window,"pointerup",u),t.event.bind(e,"pointerdown",f),t.event.bind(e,"pointermove",p),t.event.bind(e,"pointerup",g)):window.MSPointerEvent&&(t.event.bind(window,"MSPointerDown",c),t.event.bind(window,"MSPointerUp",u),t.event.bind(e,"MSPointerDown",f),t.event.bind(e,"MSPointerMove",p),t.event.bind(e,"MSPointerUp",g)))}var o=e("../../lib/helper"),r=e("../instances"),a=e("../update-geometry"),s=e("../update-scroll");t.exports=function(e){if(o.env.supportsTouch||o.env.supportsIePointer){n(e,r.get(e),o.env.supportsTouch,o.env.supportsIePointer)}}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],17:[function(e,t,i){"use strict";var n=e("../lib/helper"),o=e("../lib/class"),r=e("./instances"),a=e("./update-geometry"),s={"click-rail":e("./handler/click-rail"),"drag-scrollbar":e("./handler/drag-scrollbar"),keyboard:e("./handler/keyboard"),wheel:e("./handler/mouse-wheel"),touch:e("./handler/touch"),selection:e("./handler/selection")},l=e("./handler/native-scroll");t.exports=function(e,t){t="object"==typeof t?t:{},o.add(e,"ps-container");var i=r.add(e);i.settings=n.extend(i.settings,t),o.add(e,"ps-theme-"+i.settings.theme),i.settings.handlers.forEach(function(t){s[t](e)}),l(e),a(e)}},{"../lib/class":2,"../lib/helper":6,"./handler/click-rail":10,"./handler/drag-scrollbar":11,"./handler/keyboard":12,"./handler/mouse-wheel":13,"./handler/native-scroll":14,"./handler/selection":15,"./handler/touch":16,"./instances":18,"./update-geometry":19}],18:[function(e,t,i){"use strict";function n(e){function t(){l.add(e,"ps-focus")}function i(){l.remove(e,"ps-focus")}var n=this;n.settings=s.clone(c),n.containerWidth=null,n.containerHeight=null,n.contentWidth=null,n.contentHeight=null,n.isRtl="rtl"===u.css(e,"direction"),n.isNegativeScroll=function(){var t=e.scrollLeft,i=null;return e.scrollLeft=-1,i=e.scrollLeft<0,e.scrollLeft=t,i}(),n.negativeScrollAdjustment=n.isNegativeScroll?e.scrollWidth-e.clientWidth:0,n.event=new d,n.ownerDocument=e.ownerDocument||document,n.scrollbarXRail=u.appendTo(u.e("div","ps-scrollbar-x-rail"),e),n.scrollbarX=u.appendTo(u.e("div","ps-scrollbar-x"),n.scrollbarXRail),n.scrollbarX.setAttribute("tabindex",0),n.event.bind(n.scrollbarX,"focus",t),n.event.bind(n.scrollbarX,"blur",i),n.scrollbarXActive=null,n.scrollbarXWidth=null,n.scrollbarXLeft=null,n.scrollbarXBottom=s.toInt(u.css(n.scrollbarXRail,"bottom")),n.isScrollbarXUsingBottom=n.scrollbarXBottom===n.scrollbarXBottom,n.scrollbarXTop=n.isScrollbarXUsingBottom?null:s.toInt(u.css(n.scrollbarXRail,"top")),n.railBorderXWidth=s.toInt(u.css(n.scrollbarXRail,"borderLeftWidth"))+s.toInt(u.css(n.scrollbarXRail,"borderRightWidth")),u.css(n.scrollbarXRail,"display","block"),n.railXMarginWidth=s.toInt(u.css(n.scrollbarXRail,"marginLeft"))+s.toInt(u.css(n.scrollbarXRail,"marginRight")),u.css(n.scrollbarXRail,"display",""),n.railXWidth=null,n.railXRatio=null,n.scrollbarYRail=u.appendTo(u.e("div","ps-scrollbar-y-rail"),e),n.scrollbarY=u.appendTo(u.e("div","ps-scrollbar-y"),n.scrollbarYRail),n.scrollbarY.setAttribute("tabindex",0),n.event.bind(n.scrollbarY,"focus",t),n.event.bind(n.scrollbarY,"blur",i),n.scrollbarYActive=null,n.scrollbarYHeight=null,n.scrollbarYTop=null,n.scrollbarYRight=s.toInt(u.css(n.scrollbarYRail,"right")),n.isScrollbarYUsingRight=n.scrollbarYRight===n.scrollbarYRight,n.scrollbarYLeft=n.isScrollbarYUsingRight?null:s.toInt(u.css(n.scrollbarYRail,"left")),n.scrollbarYOuterWidth=n.isRtl?s.outerWidth(n.scrollbarY):null,n.railBorderYWidth=s.toInt(u.css(n.scrollbarYRail,"borderTopWidth"))+s.toInt(u.css(n.scrollbarYRail,"borderBottomWidth")),u.css(n.scrollbarYRail,"display","block"),n.railYMarginHeight=s.toInt(u.css(n.scrollbarYRail,"marginTop"))+s.toInt(u.css(n.scrollbarYRail,"marginBottom")),u.css(n.scrollbarYRail,"display",""),n.railYHeight=null,n.railYRatio=null}function o(e){return e.getAttribute("data-ps-id")}function r(e,t){e.setAttribute("data-ps-id",t)}function a(e){e.removeAttribute("data-ps-id")}var s=e("../lib/helper"),l=e("../lib/class"),c=e("./default-setting"),u=e("../lib/dom"),d=e("../lib/event-manager"),h=e("../lib/guid"),f={};i.add=function(e){var t=h();return r(e,t),f[t]=new n(e),f[t]},i.remove=function(e){delete f[o(e)],a(e)},i.get=function(e){return f[o(e)]}},{"../lib/class":2,"../lib/dom":3,"../lib/event-manager":4,"../lib/guid":5,"../lib/helper":6,"./default-setting":8}],19:[function(e,t,i){"use strict";function n(e,t){return e.settings.minScrollbarLength&&(t=Math.max(t,e.settings.minScrollbarLength)),e.settings.maxScrollbarLength&&(t=Math.min(t,e.settings.maxScrollbarLength)),t}function o(e,t){var i={width:t.railXWidth};t.isRtl?i.left=t.negativeScrollAdjustment+e.scrollLeft+t.containerWidth-t.contentWidth:i.left=e.scrollLeft,t.isScrollbarXUsingBottom?i.bottom=t.scrollbarXBottom-e.scrollTop:i.top=t.scrollbarXTop+e.scrollTop,s.css(t.scrollbarXRail,i);var n={top:e.scrollTop,height:t.railYHeight};t.isScrollbarYUsingRight?t.isRtl?n.right=t.contentWidth-(t.negativeScrollAdjustment+e.scrollLeft)-t.scrollbarYRight-t.scrollbarYOuterWidth:n.right=t.scrollbarYRight-e.scrollLeft:t.isRtl?n.left=t.negativeScrollAdjustment+e.scrollLeft+2*t.containerWidth-t.contentWidth-t.scrollbarYLeft-t.scrollbarYOuterWidth:n.left=t.scrollbarYLeft+e.scrollLeft,s.css(t.scrollbarYRail,n),s.css(t.scrollbarX,{left:t.scrollbarXLeft,width:t.scrollbarXWidth-t.railBorderXWidth}),s.css(t.scrollbarY,{top:t.scrollbarYTop,height:t.scrollbarYHeight-t.railBorderYWidth})}var r=e("../lib/helper"),a=e("../lib/class"),s=e("../lib/dom"),l=e("./instances"),c=e("./update-scroll");t.exports=function(e){var t=l.get(e);t.containerWidth=e.clientWidth,t.containerHeight=e.clientHeight,t.contentWidth=e.scrollWidth,t.contentHeight=e.scrollHeight;var i;e.contains(t.scrollbarXRail)||(i=s.queryChildren(e,".ps-scrollbar-x-rail"),i.length>0&&i.forEach(function(e){s.remove(e)}),s.appendTo(t.scrollbarXRail,e)),e.contains(t.scrollbarYRail)||(i=s.queryChildren(e,".ps-scrollbar-y-rail"),i.length>0&&i.forEach(function(e){s.remove(e)}),s.appendTo(t.scrollbarYRail,e)),!t.settings.suppressScrollX&&t.containerWidth+t.settings.scrollXMarginOffset<t.contentWidth?(t.scrollbarXActive=!0,t.railXWidth=t.containerWidth-t.railXMarginWidth,t.railXRatio=t.containerWidth/t.railXWidth,t.scrollbarXWidth=n(t,r.toInt(t.railXWidth*t.containerWidth/t.contentWidth)),t.scrollbarXLeft=r.toInt((t.negativeScrollAdjustment+e.scrollLeft)*(t.railXWidth-t.scrollbarXWidth)/(t.contentWidth-t.containerWidth))):t.scrollbarXActive=!1,!t.settings.suppressScrollY&&t.containerHeight+t.settings.scrollYMarginOffset<t.contentHeight?(t.scrollbarYActive=!0,t.railYHeight=t.containerHeight-t.railYMarginHeight,t.railYRatio=t.containerHeight/t.railYHeight,t.scrollbarYHeight=n(t,r.toInt(t.railYHeight*t.containerHeight/t.contentHeight)),t.scrollbarYTop=r.toInt(e.scrollTop*(t.railYHeight-t.scrollbarYHeight)/(t.contentHeight-t.containerHeight))):t.scrollbarYActive=!1,t.scrollbarXLeft>=t.railXWidth-t.scrollbarXWidth&&(t.scrollbarXLeft=t.railXWidth-t.scrollbarXWidth),t.scrollbarYTop>=t.railYHeight-t.scrollbarYHeight&&(t.scrollbarYTop=t.railYHeight-t.scrollbarYHeight),o(e,t),t.scrollbarXActive?a.add(e,"ps-active-x"):(a.remove(e,"ps-active-x"),t.scrollbarXWidth=0,t.scrollbarXLeft=0,c(e,"left",0)),t.scrollbarYActive?a.add(e,"ps-active-y"):(a.remove(e,"ps-active-y"),t.scrollbarYHeight=0,t.scrollbarYTop=0,c(e,"top",0))}},{"../lib/class":2,"../lib/dom":3,"../lib/helper":6,"./instances":18,"./update-scroll":20}],20:[function(e,t,i){"use strict";var n,o,r=e("./instances"),a=function(e){var t=document.createEvent("Event");return t.initEvent(e,!0,!0),t};t.exports=function(e,t,i){if(void 0===e)throw"You must provide an element to the update-scroll function";if(void 0===t)throw"You must provide an axis to the update-scroll function";if(void 0===i)throw"You must provide a value to the update-scroll function";"top"===t&&i<=0&&(e.scrollTop=i=0,e.dispatchEvent(a("ps-y-reach-start"))),"left"===t&&i<=0&&(e.scrollLeft=i=0,e.dispatchEvent(a("ps-x-reach-start")));var s=r.get(e);"top"===t&&i>=s.contentHeight-s.containerHeight&&(i=s.contentHeight-s.containerHeight,i-e.scrollTop<=1?i=e.scrollTop:e.scrollTop=i,e.dispatchEvent(a("ps-y-reach-end"))),"left"===t&&i>=s.contentWidth-s.containerWidth&&(i=s.contentWidth-s.containerWidth,i-e.scrollLeft<=1?i=e.scrollLeft:e.scrollLeft=i,e.dispatchEvent(a("ps-x-reach-end"))),n||(n=e.scrollTop),o||(o=e.scrollLeft),"top"===t&&i<n&&e.dispatchEvent(a("ps-scroll-up")),"top"===t&&i>n&&e.dispatchEvent(a("ps-scroll-down")),"left"===t&&i<o&&e.dispatchEvent(a("ps-scroll-left")),"left"===t&&i>o&&e.dispatchEvent(a("ps-scroll-right")),"top"===t&&(e.scrollTop=n=i,e.dispatchEvent(a("ps-scroll-y"))),"left"===t&&(e.scrollLeft=o=i,e.dispatchEvent(a("ps-scroll-x")))}},{"./instances":18}],21:[function(e,t,i){"use strict";var n=e("../lib/helper"),o=e("../lib/dom"),r=e("./instances"),a=e("./update-geometry"),s=e("./update-scroll");t.exports=function(e){var t=r.get(e);t&&(t.negativeScrollAdjustment=t.isNegativeScroll?e.scrollWidth-e.clientWidth:0,o.css(t.scrollbarXRail,"display","block"),o.css(t.scrollbarYRail,"display","block"),t.railXMarginWidth=n.toInt(o.css(t.scrollbarXRail,"marginLeft"))+n.toInt(o.css(t.scrollbarXRail,"marginRight")),t.railYMarginHeight=n.toInt(o.css(t.scrollbarYRail,"marginTop"))+n.toInt(o.css(t.scrollbarYRail,"marginBottom")),o.css(t.scrollbarXRail,"display","none"),o.css(t.scrollbarYRail,"display","none"),a(e),s(e,"top",e.scrollTop),s(e,"left",e.scrollLeft),o.css(t.scrollbarXRail,"display",""),o.css(t.scrollbarYRail,"display",""))}},{"../lib/dom":3,"../lib/helper":6,"./instances":18,"./update-geometry":19,"./update-scroll":20}]},{},[1]),define("WoltLabSuite/Core/Date/Util",["Language"],function(e){"use strict";return{formatDate:function(t){return this.format(t,e.get("wcf.date.dateFormat"))},formatTime:function(t){return this.format(t,e.get("wcf.date.timeFormat"))},formatDateTime:function(t){return this.format(t,e.get("wcf.date.dateTimeFormat").replace(/%date%/,e.get("wcf.date.dateFormat")).replace(/%time%/,e.get("wcf.date.timeFormat")))},format:function(t,i){var n,o="";"c"===i&&(i="Y-m-dTH:i:sP");for(var r=0,a=i.length;r<a;r++){switch(i[r]){case"s":n=("0"+t.getSeconds().toString()).slice(-2);break;case"i":n=t.getMinutes(),n<10&&(n="0"+n);break;case"a":n=t.getHours()>11?"pm":"am";break;case"g":n=t.getHours(),0===n?n=12:n>12&&(n-=12);break;case"h":n=t.getHours(),0===n?n=12:n>12&&(n-=12),n=("0"+n.toString()).slice(-2);break;case"A":n=t.getHours()>11?"PM":"AM";break;case"G":n=t.getHours();break;case"H":n=t.getHours(),n=("0"+n.toString()).slice(-2);break;case"d":n=t.getDate(),n=("0"+n.toString()).slice(-2);break;case"j":n=t.getDate();break;case"l":n=e.get("__days")[t.getDay()];break;case"D":n=e.get("__daysShort")[t.getDay()];break;case"S":n="";break;case"m":n=t.getMonth()+1,n=("0"+n.toString()).slice(-2);break;case"n":n=t.getMonth()+1;break;case"F":n=e.get("__months")[t.getMonth()];break;case"M":n=e.get("__monthsShort")[t.getMonth()];break;case"y":n=t.getYear().toString().replace(/^\d{2}/,"");break;case"Y":n=t.getFullYear();break;case"P":var s=t.getTimezoneOffset();n=s>0?"-":"+",s=Math.abs(s),n+=("0"+(~~(s/60)).toString()).slice(-2),n+=":",n+=("0"+(s%60).toString()).slice(-2);break;case"r":n=t.toString();break;case"U":n=Math.round(t.getTime()/1e3);break;case"\\":n="",r+1<a&&(n=i[++r]);break;default:n=i[r]}o+=n}return o},gmdate:function(e){return e instanceof Date||(e=new Date),Math.round(Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDay(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds())/1e3)},getTimeElement:function(t){var i=elCreate("time");i.className="datetime";var n=this.formatDate(t),o=this.formatTime(t);return elAttr(i,"datetime",this.format(t,"c")),elData(i,"timestamp",(t.getTime()-t.getMilliseconds())/1e3),elData(i,"date",n),elData(i,"time",o),elData(i,"offset",60*t.getTimezoneOffset()),t.getTime()>Date.now()&&(elData(i,"is-future-date","true"),i.textContent=e.get("wcf.date.dateTimeFormat").replace("%time%",o).replace("%date%",n)),i},getTimezoneDate:function(e,t){var i=new Date(e),n=6e4*i.getTimezoneOffset();return new Date(e+n+t)}}}),define("WoltLabSuite/Core/Timer/Repeating",[],function(){"use strict";function e(e,t){if("function"!=typeof e)throw new TypeError("Expected a valid callback as first argument.");if(t<0||t>864e5)throw new RangeError("Invalid delta "+t+". Delta must be in the interval [0, 86400000].");this._callback=e.bind(void 0,this),this._delta=t,this._timer=void 0,this.restart()}return e.prototype={restart:function(){this.stop(),this._timer=setInterval(this._callback,this._delta)},stop:function(){void 0!==this._timer&&(clearInterval(this._timer),this._timer=void 0)},setDelta:function(e){this._delta=e,this.restart()}},e}),define("WoltLabSuite/Core/Date/Time/Relative",["Dom/ChangeListener","Language","WoltLabSuite/Core/Date/Util","WoltLabSuite/Core/Timer/Repeating"],function(e,t,i,n){"use strict";var o=elByTag("time"),r=!0,a=!1,s=null;return{setup:function(){new n(this._refresh.bind(this),6e4),e.add("WoltLabSuite/Core/Date/Time/Relative",this._refresh.bind(this)),document.addEventListener("visibilitychange",this._onVisibilityChange.bind(this))},_onVisibilityChange:function(){document.hidden?(r=!1,a=!1):(r=!0,a&&(this._refresh(),a=!1))},_refresh:function(){if(!r)return void(a||(a=!0));var e=new Date,n=(e.getTime()-e.getMilliseconds())/1e3;null===s&&(s=n-window.TIME_NOW);for(var l=0,c=o.length;l<c;l++){var u=o[l];if(u.classList.contains("datetime")&&!elData(u,"is-future-date")){var d=~~elData(u,"timestamp")+s,h=elData(u,"date"),f=elData(u,"time"),p=elData(u,"offset");if(elAttr(u,"title")||elAttr(u,"title",t.get("wcf.date.dateTimeFormat").replace(/%date%/,h).replace(/%time%/,f)),d>=n||n<d+60)u.textContent=t.get("wcf.date.relative.now");else if(n<d+3540){var g=Math.max(Math.round((n-d)/60),1);u.textContent=t.get("wcf.date.relative.minutes",{minutes:g})}else if(n<d+86400){var m=Math.round((n-d)/3600);u.textContent=t.get("wcf.date.relative.hours",{hours:m})}else if(n<d+518400){var v=new Date(e.getFullYear(),e.getMonth(),e.getDate()),b=Math.ceil((v/1e3-d)/86400),_=i.getTimezoneDate(1e3*d,1e3*p),y=_.getDay(),w=t.get("__days")[y];u.textContent=t.get("wcf.date.relative.pastDays",{days:b,day:w,time:f})}else u.textContent=t.get("wcf.date.shortDateTimeFormat").replace(/%date%/,h).replace(/%time%/,f)}}}}}),define("WoltLabSuite/Core/Ui/Page/Menu/Abstract",["Core","Environment","EventHandler","Language","ObjectMap","Dom/Traverse","Dom/Util","Ui/Screen"],function(e,t,i,n,o,r,a,s){"use strict";function l(e,t,i){this.init(e,t,i)}var c=elById("pageContainer"),u="";return l.prototype={init:function(e,n,r){if("packageInstallationSetup"!==elData(document.body,"template")){this._activeList=[],this._depth=0,this._enabled=!0,this._eventIdentifier=e,this._items=new o,this._menu=elById(n),this._removeActiveList=!1;var s=this.open.bind(this);this._button=elBySel(r),this._button.addEventListener(WCF_CLICK_EVENT,s),this._initItems(),this._initHeader(),i.add(this._eventIdentifier,"open",s),i.add(this._eventIdentifier,"close",this.close.bind(this)),i.add(this._eventIdentifier,"updateButtonState",this._updateButtonState.bind(this));var l,c=elByClass("menuOverlayItemList",this._menu);this._menu.addEventListener("animationend",function(){if(!this._menu.classList.contains("open"))for(var e=0,t=c.length;e<t;e++)l=c[e],l.classList.remove("active"),l.classList.remove("hidden")}.bind(this)),this._menu.children[0].addEventListener("transitionend",function(){if(this._menu.classList.add("allowScroll"),this._removeActiveList){this._removeActiveList=!1;var e=this._activeList.pop();e&&e.classList.remove("activeList")}}.bind(this));var u=elCreate("div");u.className="menuOverlayMobileBackdrop",u.addEventListener(WCF_CLICK_EVENT,this.close.bind(this)),a.insertAfter(u,this._menu),this._updateButtonState(),"android"===t.platform()&&this._initializeAndroid()}},open:function(e){return!!this._enabled&&(e instanceof Event&&e.preventDefault(),this._menu.classList.add("open"),this._menu.classList.add("allowScroll"),this._menu.children[0].classList.add("activeList"),s.scrollDisable(),c.classList.add("menuOverlay-"+this._menu.id),s.pageOverlayOpen(),!0)},close:function(e){return e instanceof Event&&e.preventDefault(),!!this._menu.classList.contains("open")&&(this._menu.classList.remove("open"),s.scrollEnable(),s.pageOverlayClose(),c.classList.remove("menuOverlay-"+this._menu.id),!0)},enable:function(){this._enabled=!0},disable:function(){this._enabled=!1,this.close(!0)},_initializeAndroid:function(){var t,i,n;switch(this._menu.id){case"pageUserMenuMobile":t="right";break;case"pageMainMenuMobile":t="left";break;default:return}i=this._menu.nextElementSibling,n=null,document.addEventListener("touchstart",function(i){var o,r,a,l;if(o=i.touches,r=this._menu.classList.contains("open"),"left"===t?(a=!r&&o[0].clientX<20,l=r&&Math.abs(this._menu.offsetWidth-o[0].clientX)<20):"right"===t&&(a=r&&Math.abs(document.body.clientWidth-this._menu.offsetWidth-o[0].clientX)<20,l=!r&&document.body.clientWidth-o[0].clientX<20),o.length>1)return void(u&&e.triggerEvent(document,"touchend"));if(!u&&(a||l)){if(s.pageOverlayIsActive()){for(var d=!1,h=0;h<c.classList.length;h++)c.classList[h]==="menuOverlay-"+this._menu.id&&(d=!0);if(!d)return}document.documentElement.classList.contains("redactorActive")||(n={x:o[0].clientX,y:o[0].clientY},a&&(u="left"),l&&(u="right"))}}.bind(this)),document.addEventListener("touchend",function(e){if(u&&null!==n){if(!this._menu.classList.contains("open"))return n=null,void(u="");var o;o=e?e.changedTouches[0].clientX:n.x,this._menu.classList.add("androidMenuTouchEnd"),this._menu.style.removeProperty("transform"),i.style.removeProperty(t),this._menu.addEventListener("transitionend",function(){this._menu.classList.remove("androidMenuTouchEnd")}.bind(this),{once:!0}),"left"===t?("left"===u&&o<n.x+100&&this.close(),"right"===u&&o<n.x-100&&this.close()):"right"===t&&("left"===u&&o>n.x+100&&this.close(),"right"===u&&o>n.x-100&&this.close()),n=null,u=""}
+}.bind(this)),document.addEventListener("touchmove",function(e){if(u&&null!==n){var o=e.touches,r=!1,a=!1;"left"===u&&(r=o[0].clientX>n.x+5),"right"===u&&(r=o[0].clientX<n.x-5),a=Math.abs(o[0].clientY-n.y)>20;var s=this._menu.classList.contains("open");if(s||!r||a||(this.open(),s=!0),s){var l=o[0].clientX;"right"===t&&(l=document.body.clientWidth-l),l>this._menu.offsetWidth&&(l=this._menu.offsetWidth),l<0&&(l=0),this._menu.style.setProperty("transform","translateX("+("left"===t?1:-1)*(l-this._menu.offsetWidth)+"px)"),i.style.setProperty(t,Math.min(this._menu.offsetWidth,l)+"px")}}}.bind(this))},_initItems:function(){elBySelAll(".menuOverlayItemLink",this._menu,this._initItem.bind(this))},_initItem:function(e){var t=e.parentNode,n=elData(t,"more");if(n)return void e.addEventListener(WCF_CLICK_EVENT,function(o){o.preventDefault(),o.stopPropagation(),i.fire(this._eventIdentifier,"more",{handler:this,identifier:n,item:e,parent:t})}.bind(this));var o,a=e.nextElementSibling;if(null!==a)if("OL"!==a.nodeName&&a.classList.contains("menuOverlayItemLinkIcon"))for(o=elCreate("span"),o.className="menuOverlayItemWrapper",t.insertBefore(o,e),o.appendChild(e);o.nextElementSibling;)o.appendChild(o.nextElementSibling);else{var s="#"!==elAttr(e,"href"),l=t.parentNode,c=elData(a,"title");this._items.set(e,{itemList:a,parentItemList:l}),""===c&&(c=r.childByClass(e,"menuOverlayItemTitle").textContent,elData(a,"title",c));var u=this._showItemList.bind(this,e);if(s){o=elCreate("span"),o.className="menuOverlayItemWrapper",t.insertBefore(o,e),o.appendChild(e);var d=elCreate("a");elAttr(d,"href","#"),d.className="menuOverlayItemLinkIcon"+(e.classList.contains("active")?" active":""),d.innerHTML='<span class="icon icon24 fa-angle-right"></span>',d.addEventListener(WCF_CLICK_EVENT,u),o.appendChild(d)}else e.classList.add("menuOverlayItemLinkMore"),e.addEventListener(WCF_CLICK_EVENT,u);var h=elCreate("li");h.className="menuOverlayHeader",o=elCreate("span"),o.className="menuOverlayItemWrapper";var f=elCreate("a");elAttr(f,"href","#"),f.className="menuOverlayItemLink menuOverlayBackLink",f.textContent=elData(l,"title"),f.addEventListener(WCF_CLICK_EVENT,this._hideItemList.bind(this,e));var p=elCreate("a");if(elAttr(p,"href","#"),p.className="menuOverlayItemLinkIcon",p.innerHTML='<span class="icon icon24 fa-times"></span>',p.addEventListener(WCF_CLICK_EVENT,this.close.bind(this)),o.appendChild(f),o.appendChild(p),h.appendChild(o),a.insertBefore(h,a.firstElementChild),!h.nextElementSibling.classList.contains("menuOverlayTitle")){var g=elCreate("li");g.className="menuOverlayTitle";var m=elCreate("span");m.textContent=c,g.appendChild(m),a.insertBefore(g,h.nextElementSibling)}}},_initHeader:function(){var e=elCreate("li");e.className="menuOverlayHeader";var t=elCreate("span");t.className="menuOverlayItemWrapper",e.appendChild(t);var i=elCreate("span");i.className="menuOverlayLogoWrapper",t.appendChild(i);var n=elCreate("span");n.className="menuOverlayLogo",n.style.setProperty("background-image",'url("'+elData(this._menu,"page-logo")+'")',""),i.appendChild(n);var o=elCreate("a");elAttr(o,"href","#"),o.className="menuOverlayItemLinkIcon",o.innerHTML='<span class="icon icon24 fa-times"></span>',o.addEventListener(WCF_CLICK_EVENT,this.close.bind(this)),t.appendChild(o);var a=r.childByClass(this._menu,"menuOverlayItemList");a.insertBefore(e,a.firstElementChild)},_hideItemList:function(e,t){t instanceof Event&&t.preventDefault(),this._menu.classList.remove("allowScroll"),this._removeActiveList=!0,this._items.get(e).parentItemList.classList.remove("hidden"),this._updateDepth(!1)},_showItemList:function(e,t){t instanceof Event&&t.preventDefault();var n=this._items.get(e),o=elData(n.itemList,"load");if(o&&!elDataBool(e,"loaded")){var r=t.currentTarget.firstElementChild;return r.classList.contains("fa-angle-right")&&(r.classList.remove("fa-angle-right"),r.classList.add("fa-spinner")),void i.fire(this._eventIdentifier,"load_"+o)}this._menu.classList.remove("allowScroll"),n.itemList.classList.add("activeList"),n.parentItemList.classList.add("hidden"),this._activeList.push(n.itemList),this._updateDepth(!0)},_updateDepth:function(e){this._depth+=e?1:-1;var t=-100*this._depth;"rtl"===n.get("wcf.global.pageDirection")&&(t*=-1),this._menu.children[0].style.setProperty("transform","translateX("+t+"%)","")},_updateButtonState:function(){var e=!1;elBySelAll(".badgeUpdate",this._menu,function(t){~~t.textContent>0&&(e=!0)}),this._button.classList[e?"add":"remove"]("pageMenuMobileButtonHasContent")}},l}),define("WoltLabSuite/Core/Ui/Page/Menu/Main",["Core","Language","Dom/Traverse","./Abstract"],function(e,t,i,n){"use strict";function o(){this.init()}var r=null,a=null,s=null,l=null,c=null;return e.inherit(o,n,{init:function(){o._super.prototype.init.call(this,"com.woltlab.wcf.MainMenuMobile","pageMainMenuMobile","#pageHeader .mainMenu"),r=elById("pageMainMenuMobilePageOptionsTitle"),null!==r&&(s=i.childByClass(r,"menuOverlayItemList"),l=elBySel(".jsPageNavigationIcons"),c=function(e){this.close(),e.stopPropagation()}.bind(this)),elAttr(this._button,"aria-label",t.get("wcf.menu.page")),elAttr(this._button,"role","button")},open:function(e){if(!o._super.prototype.open.call(this,e))return!1;if(null===r)return!0;if(a=l&&l.childElementCount>0){for(var t,i;l.childElementCount;)t=l.children[0],t.classList.add("menuOverlayItem"),t.classList.add("menuOverlayItemOption"),t.addEventListener(WCF_CLICK_EVENT,c),i=t.children[0],i.classList.add("menuOverlayItemLink"),i.classList.add("box24"),i.children[1].classList.remove("invisible"),i.children[1].classList.add("menuOverlayItemTitle"),r.parentNode.insertBefore(t,r.nextSibling);elShow(r)}else elHide(r);return!0},close:function(e){if(!o._super.prototype.close.call(this,e))return!1;if(a){elHide(r);for(var t,i=r.nextElementSibling;i&&i.classList.contains("menuOverlayItemOption");)i.classList.remove("menuOverlayItem"),i.classList.remove("menuOverlayItemOption"),i.removeEventListener(WCF_CLICK_EVENT,c),t=i.children[0],t.classList.remove("menuOverlayItemLink"),t.classList.remove("box24"),t.children[1].classList.add("invisible"),t.children[1].classList.remove("menuOverlayItemTitle"),l.appendChild(i),i=i.nextElementSibling}return!0}}),o}),define("WoltLabSuite/Core/Ui/Page/Menu/User",["Core","EventHandler","Language","./Abstract"],function(e,t,i,n){"use strict";function o(){this.init()}return e.inherit(o,n,{init:function(){var e=elBySel("#pageUserMenuMobile > .menuOverlayItemList");if(1===e.childElementCount&&e.children[0].classList.contains("menuOverlayTitle"))return void elBySel("#pageHeader .userPanel").classList.add("hideUserPanel");o._super.prototype.init.call(this,"com.woltlab.wcf.UserMenuMobile","pageUserMenuMobile","#pageHeader .userPanel"),t.add("com.woltlab.wcf.userMenu","updateBadge",function(e){elBySelAll(".menuOverlayItemBadge",this._menu,function(t){if(elData(t,"badge-identifier")===e.identifier){var i=elBySel(".badge",t);e.count?(null===i&&(i=elCreate("span"),i.className="badge badgeUpdate",t.appendChild(i)),i.textContent=e.count):null!==i&&elRemove(i),this._updateButtonState()}}.bind(this))}.bind(this)),elAttr(this._button,"aria-label",i.get("wcf.menu.user")),elAttr(this._button,"role","button")},close:function(e){var t=WCF.Dropdown.Interactive.Handler.getOpenDropdown();t?(e.preventDefault(),e.stopPropagation(),t.close()):o._super.prototype.close.call(this,e)}}),o}),define("WoltLabSuite/Core/Ui/Dropdown/Reusable",["Dictionary","Ui/SimpleDropdown"],function(e,t){"use strict";function i(e){if(!n.has(e))throw new Error("Unknown dropdown identifier '"+e+"'");return n.get(e)}var n=new e,o=0;return{init:function(e,i){if(!n.has(e)){var r=elCreate("div");r.id="reusableDropdownGhost"+o++,t.initFragment(r,i),n.set(e,r.id)}},getDropdownMenu:function(e){return t.getDropdownMenu(i(e))},registerCallback:function(e,n){t.registerCallback(i(e),n)},toggleDropdown:function(e,n){t.toggleDropdown(i(e),n)}}}),define("WoltLabSuite/Core/Ui/Mobile",["Core","Environment","EventHandler","Language","List","Dom/ChangeListener","Dom/Traverse","Ui/Alignment","Ui/CloseOverlay","Ui/Screen","./Page/Menu/Main","./Page/Menu/User","WoltLabSuite/Core/Ui/Dropdown/Reusable"],function(e,t,i,n,o,r,a,s,l,c,u,d,h){"use strict";var f=elByClass("buttonGroupNavigation"),p=null,g=null,m=null,v=!1,b=new o,_=null,y=elByClass("message"),w={},C=null,E=null,L=null,A=[],S=!1;return{setup:function(i){w=e.extend({enableMobileMenu:!0},i),_=elById("main"),elBySelAll(".sidebar",void 0,function(e){A.push(e)}),t.touch()&&document.documentElement.classList.add("touch"),"desktop"!==t.platform()&&document.documentElement.classList.add("mobile");var n=elBySel(".messageGroupList");n&&(L=elByClass("messageGroup",n)),c.on("screen-md-down",{match:this.enable.bind(this),unmatch:this.disable.bind(this),setup:this._init.bind(this)}),c.on("screen-sm-down",{match:this.enableShadow.bind(this),unmatch:this.disableShadow.bind(this),setup:this.enableShadow.bind(this)}),c.on("screen-xs",{match:this._enableSidebarXS.bind(this),unmatch:this._disableSidebarXS.bind(this),setup:this._setupSidebarXS.bind(this)})},enable:function(){v=!0,w.enableMobileMenu&&(C.enable(),E.enable())},enableShadow:function(){L&&this.rebuildShadow(L,".messageGroupLink")},disable:function(){v=!1,w.enableMobileMenu&&(C.disable(),E.disable())},disableShadow:function(){L&&this.removeShadow(L),g&&p()},_init:function(){v=!0,this._initSearchBar(),this._initButtonGroupNavigation(),this._initMessages(),this._initMobileMenu(),l.add("WoltLabSuite/Core/Ui/Mobile",this._closeAllMenus.bind(this)),r.add("WoltLabSuite/Core/Ui/Mobile",function(){this._initButtonGroupNavigation(),this._initMessages()}.bind(this))},_initSearchBar:function(){var e=elById("pageHeaderSearch"),n=elById("pageHeaderSearchInput"),o=null;i.add("com.woltlab.wcf.MainMenuMobile","more",function(i){"com.woltlab.wcf.search"===i.identifier&&(i.handler.close(!0),"ios"===t.platform()&&(o=document.body.scrollTop,c.scrollDisable()),e.style.setProperty("top",elById("pageHeader").offsetHeight+"px",""),e.classList.add("open"),n.focus(),"ios"===t.platform()&&(document.body.scrollTop=0))}),_.addEventListener(WCF_CLICK_EVENT,function(){e&&e.classList.remove("open"),"ios"===t.platform()&&null!==o&&(c.scrollEnable(),document.body.scrollTop=o,o=null)})},_initButtonGroupNavigation:function(){for(var e=0,t=f.length;e<t;e++){var i=f[e];if(!i.classList.contains("jsMobileButtonGroupNavigation")){i.classList.add("jsMobileButtonGroupNavigation");var n=elBySel(".buttonList",i);if(0!==n.childElementCount){i.parentNode.classList.add("hasMobileNavigation");var o=elCreate("a");o.className="dropdownLabel";var r=elCreate("span");r.className="icon icon24 fa-ellipsis-v",o.appendChild(r),function(e,t,i){t.addEventListener(WCF_CLICK_EVENT,function(t){t.preventDefault(),t.stopPropagation(),e.classList.toggle("open")}),i.addEventListener(WCF_CLICK_EVENT,function(t){t.stopPropagation(),e.classList.remove("open")})}(i,o,n),i.insertBefore(o,i.firstChild)}}}},_initMessages:function(){Array.prototype.forEach.call(y,function(e){if(!b.has(e)){var t=elBySel(".jsMobileNavigation",e);if(t){t.addEventListener(WCF_CLICK_EVENT,function(e){e.stopPropagation(),window.setTimeout(function(){t.classList.remove("open")},10)});var i=elBySel(".messageQuickOptions",e);i&&t.childElementCount&&(i.classList.add("active"),i.addEventListener(WCF_CLICK_EVENT,function(n){v&&"LABEL"!==n.target.nodeName&&"INPUT"!==n.target.nodeName&&(n.preventDefault(),n.stopPropagation(),this._toggleMobileNavigation(e,i,t))}.bind(this)))}b.add(e)}}.bind(this))},_initMobileMenu:function(){w.enableMobileMenu&&(C=new u,E=new d)},_closeAllMenus:function(){elBySelAll(".jsMobileButtonGroupNavigation.open, .jsMobileNavigation.open",null,function(e){e.classList.remove("open")}),v&&g&&p()},rebuildShadow:function(e,t){for(var i,n,o,r=0,s=e.length;r<s;r++)i=e[r],n=i.parentNode,null===(o=a.childByClass(n,"mobileLinkShadow"))&&elBySel(t,i).href&&(o=elCreate("a"),o.className="mobileLinkShadow",o.href=elBySel(t,i).href,n.appendChild(o),n.classList.add("mobileLinkShadowContainer"))},removeShadow:function(e){for(var t,i,n,o=0,r=e.length;o<r;o++)t=e[o],i=t.parentNode,i.classList.contains("mobileLinkShadowContainer")&&(n=a.childByClass(i,"mobileLinkShadow"),null!==n&&elRemove(n),i.classList.remove("mobileLinkShadowContainer"))},_enableSidebarXS:function(){S=!0},_disableSidebarXS:function(){S=!1,A.forEach(function(e){e.classList.remove("open")})},_setupSidebarXS:function(){A.forEach(function(e){e.addEventListener("mousedown",function(t){S&&t.target===e&&(t.preventDefault(),e.classList.toggle("open"))})}),S=!0},_toggleMobileNavigation:function(e,t,i){if(null===g)g=elCreate("ul"),g.className="dropdownMenu",h.init("com.woltlab.wcf.jsMobileNavigation",g),p=function(){g.classList.remove("dropdownOpen")};else if(g.classList.contains("dropdownOpen")&&(p(),m===e))return;g.innerHTML="",l.execute(),this._rebuildMobileNavigation(i);var n=i.previousElementSibling;if(n&&n.classList.contains("messageFooterButtonsExtra")){var o=elCreate("li");o.className="dropdownDivider",g.appendChild(o),this._rebuildMobileNavigation(n)}s.set(g,t,{horizontal:"right",allowFlip:"vertical"}),g.classList.add("dropdownOpen"),m=e},_rebuildMobileNavigation:function(t){elBySelAll(".button:not(.ignoreMobileNavigation)",t,function(t){var i=elCreate("li");t.classList.contains("active")&&(i.className="active"),i.innerHTML='<a href="#">'+elBySel("span:not(.icon)",t).textContent+"</a>",i.children[0].addEventListener(WCF_CLICK_EVENT,function(i){i.preventDefault(),i.stopPropagation(),"A"===t.nodeName?t.click():e.triggerEvent(t,WCF_CLICK_EVENT),p()}),g.appendChild(i)})}}}),define("WoltLabSuite/Core/Ui/TabMenu/Simple",["Dictionary","EventHandler","Dom/Traverse","Dom/Util"],function(e,t,i,n){"use strict";function o(t){this._container=t,this._containers=new e,this._isLegacy=null,this._store=null,this._tabs=new e}return o.prototype={validate:function(){if(!this._container.classList.contains("tabMenuContainer"))return!1;var e=i.childByTag(this._container,"NAV");if(null===e)return!1;var t=elByTag("li",e);if(0===t.length)return!1;var o,r,a,s,l=i.childrenByTag(this._container,"DIV");for(a=0,s=l.length;a<s;a++)o=l[a],r=elData(o,"name"),r||(r=n.identify(o)),elData(o,"name",r),this._containers.set(r,o);var c,u=this._container.id;for(a=0,s=t.length;a<s;a++)if(c=t[a],r=this._getTabName(c)){if(this._tabs.has(r))throw new Error("Tab names must be unique, li[data-name='"+r+"'] (tab menu id: '"+u+"') exists more than once.");if(void 0===(o=this._containers.get(r)))throw new Error("Expected content element for li[data-name='"+r+"'] (tab menu id: '"+u+"').");if(o.parentNode!==this._container)throw new Error("Expected content element '"+r+"' (tab menu id: '"+u+"') to be a direct children.");if(1!==c.childElementCount||"A"!==c.children[0].nodeName)throw new Error("Expected exactly one <a> as children for li[data-name='"+r+"'] (tab menu id: '"+u+"').");this._tabs.set(r,c)}if(!this._tabs.size)throw new Error("Expected at least one tab (tab menu id: '"+u+"').");return this._isLegacy&&(elData(this._container,"is-legacy",!0),this._tabs.forEach(function(e,t){elAttr(e,"aria-controls",t)})),!0},init:function(e){e=e||null,this._tabs.forEach(function(t){e&&e.get(elData(t,"name"))===t||t.children[0].addEventListener(WCF_CLICK_EVENT,this._onClick.bind(this))}.bind(this));var t=null;if(!e){var i=o.getIdentifierFromHash(),n=null;if(""!==i&&(n=this._tabs.get(i))&&this._container.parentNode.classList.contains("tabMenuContainer")&&(t=this._container),!n){var r=elData(this._container,"preselect")||elData(this._container,"active");"true"!==r&&r||(r=!0),!0===r?this._tabs.forEach(function(e){n||elIsHidden(e)||e.previousElementSibling&&!elIsHidden(e.previousElementSibling)||(n=e)}):"false"!==r&&(n=this._tabs.get(r))}n&&(this._containers.forEach(function(e){e.classList.add("hidden")}),this.select(null,n,!0));var a=elData(this._container,"store");if(a){var s=elCreate("input");s.type="hidden",s.name=a,s.value=elData(this.getActiveTab(),"name"),this._container.appendChild(s),this._store=s}}return t},select:function(e,i,n){if(!(i=i||this._tabs.get(e))){if(~~e==e){e=~~e;var r=0;this._tabs.forEach(function(t){r===e&&(i=t),r++})}if(!i)throw new Error("Expected a valid tab name, '"+e+"' given (tab menu id: '"+this._container.id+"').")}e=e||elData(i,"name");var a=this.getActiveTab(),s=null;if(a){var l=elData(a,"name");if(l===e)return;n||t.fire("com.woltlab.wcf.simpleTabMenu_"+this._container.id,"beforeSelect",{tab:a,tabName:l}),a.classList.remove("active"),s=this._containers.get(elData(a,"name")),s.classList.remove("active"),s.classList.add("hidden"),this._isLegacy&&(a.classList.remove("ui-state-active"),s.classList.remove("ui-state-active"))}i.classList.add("active");var c=this._containers.get(e);if(c.classList.add("active"),c.classList.remove("hidden"),this._isLegacy&&(i.classList.add("ui-state-active"),c.classList.add("ui-state-active")),this._store&&(this._store.value=e),!n){t.fire("com.woltlab.wcf.simpleTabMenu_"+this._container.id,"select",{active:i,activeName:e,previous:a,previousName:a?elData(a,"name"):null});var u=this._isLegacy&&"function"==typeof window.jQuery?window.jQuery:null;u&&u(this._container).trigger("wcftabsbeforeactivate",{newTab:u(i),oldTab:u(a),newPanel:u(c),oldPanel:u(s)});var d=window.location.href.replace(/#+[^#]*$/,"");o.getIdentifierFromHash()===e?d+=window.location.hash:d+="#"+e,window.history.replaceState(void 0,void 0,d)}require(["WoltLabSuite/Core/Ui/TabMenu"],function(e){e.scrollToTab(i)})},selectFirstVisible:function(){var e;return this._tabs.forEach(function(t){e||elIsHidden(t)||(e=t)}.bind(this)),e&&this.select(void 0,e,!1),!!e},rebuild:function(){var t=new e;t.merge(this._tabs),this.validate(),this.init(t)},hasTab:function(e){return this._tabs.has(e)},_onClick:function(e){e.preventDefault(),this.select(null,e.currentTarget.parentNode)},_getTabName:function(e){var t=elData(e,"name");return t||1===e.childElementCount&&"A"===e.children[0].nodeName&&e.children[0].href.match(/#([^#]+)$/)&&(t=RegExp.$1,null===elById(t)?t=null:(this._isLegacy=!0,elData(e,"name",t))),t},getActiveTab:function(){return elBySel("#"+this._container.id+" > nav > ul > li.active")},getContainers:function(){return this._containers},getTabs:function(){return this._tabs}},o.getIdentifierFromHash=function(){return window.location.hash.match(/^#+([^\/]+)+(?:\/.+)?/)?RegExp.$1:""},o}),define("WoltLabSuite/Core/Ui/TabMenu",["Dictionary","EventHandler","Dom/ChangeListener","Dom/Util","Ui/CloseOverlay","Ui/Screen","./TabMenu/Simple"],function(e,t,i,n,o,r,a){"use strict";var s=null,l=!1,c=new e;return{setup:function(){this._init(),this._selectErroneousTabs(),i.add("WoltLabSuite/Core/Ui/TabMenu",this._init.bind(this)),o.add("WoltLabSuite/Core/Ui/TabMenu",function(){s&&(s.classList.remove("active"),s=null)}),r.on("screen-sm-down",{enable:this._scrollEnable.bind(this,!1),disable:this._scrollDisable.bind(this),setup:this._scrollEnable.bind(this,!0)}),window.addEventListener("hashchange",function(){var e=a.getIdentifierFromHash(),t=e?elById(e):null;null!==t&&t.classList.contains("tabMenuContent")&&c.forEach(function(t){t.hasTab(e)&&t.select(e)})});var e=a.getIdentifierFromHash();e&&window.setTimeout(function(){var t=elById(e);if(t&&t.classList.contains("tabMenuContent")){var i=window.scrollY||window.pageYOffset;if(i>0){var o=t.parentNode,r=o.offsetTop-50;if(r<0&&(r=0),i>r){var a=n.offset(o).top;a<=50?a=0:a-=50,window.scrollTo(0,a)}}}},100)},_init:function(){for(var e,t,i,o,r,l=elBySelAll(".tabMenuContainer:not(.staticTabMenuContainer)"),u=0,d=l.length;u<d;u++)e=l[u],t=n.identify(e),c.has(t)||(r=new a(e),r.validate()&&(o=r.init(),c.set(t,r),o instanceof Element&&(r=this.getTabMenu(o.parentNode.id),r.select(o.id,null,!0)),i=elBySel("#"+t+" > nav > ul"),function(e){e.addEventListener(WCF_CLICK_EVENT,function(t){t.preventDefault(),t.stopPropagation(),t.target===e?(e.classList.add("active"),s=e):(e.classList.remove("active"),s=null)})}(i),elBySelAll(".tabMenu, .menu",e,function(e){var t=this._rebuildMenuOverflow.bind(this,e),i=null;elBySel("ul",e).addEventListener("scroll",function(){null!==i&&window.clearTimeout(i),i=window.setTimeout(t,10)})}.bind(this))))},_selectErroneousTabs:function(){c.forEach(function(e){var t=!1;e.getContainers().forEach(function(i){!t&&elByClass("formError",i).length&&(t=!0,e.select(i.id))})})},getTabMenu:function(e){return c.get(e)},_scrollEnable:function(e){l=!0,c.forEach(function(t){var i=t.getActiveTab();e?this._rebuildMenuOverflow(i.closest(".menu, .tabMenu")):this.scrollToTab(i)}.bind(this))},_scrollDisable:function(){l=!1},scrollToTab:function(e){if(l){var t=e.closest("ul"),i=t.clientWidth,n=t.scrollLeft,o=t.scrollWidth;if(i!==o){var r=e.offsetLeft,a=!1;r<n&&(a=!0);var s=!1;if(!a){var c=i-(r-n),u=e.clientWidth;null!==e.nextElementSibling&&(s=!0,u+=20),c<u&&(a=!0)}a&&this._scrollMenu(t,r,n,o,i,s)}}},_scrollMenu:function(e,t,i,n,o,r){r?t-=15:t>0&&(t-=15),t=t<0?0:Math.min(t,n-o),i!==t&&(e.classList.add("enableAnimation"),i<t?e.firstElementChild.style.setProperty("margin-left",i-t+"px",""):e.style.setProperty("padding-left",i-t+"px",""),setTimeout(function(){e.classList.remove("enableAnimation"),e.firstElementChild.style.removeProperty("margin-left"),e.style.removeProperty("padding-left"),e.scrollLeft=t},300))},_rebuildMenuOverflow:function(e){if(l){var t=e.clientWidth,i=elBySel("ul",e),n=i.scrollLeft,o=i.scrollWidth,r=n>0,a=elBySel(".tabMenuOverlayLeft",e);r?(null===a&&(a=elCreate("span"),a.className="tabMenuOverlayLeft icon icon24 fa-angle-left",a.addEventListener(WCF_CLICK_EVENT,function(){var e=i.clientWidth;this._scrollMenu(i,i.scrollLeft-~~(e/2),i.scrollLeft,i.scrollWidth,e,0)}.bind(this)),e.insertBefore(a,e.firstChild)),a.classList.add("active")):null!==a&&a.classList.remove("active");var s=t+n<o,c=elBySel(".tabMenuOverlayRight",e);s?(null===c&&(c=elCreate("span"),c.className="tabMenuOverlayRight icon icon24 fa-angle-right",c.addEventListener(WCF_CLICK_EVENT,function(){var e=i.clientWidth;this._scrollMenu(i,i.scrollLeft+~~(e/2),i.scrollLeft,i.scrollWidth,e,0)}.bind(this)),e.appendChild(c)),c.classList.add("active")):null!==c&&c.classList.remove("active")}}}}),define("WoltLabSuite/Core/Ui/FlexibleMenu",["Core","Dictionary","Dom/ChangeListener","Dom/Traverse","Dom/Util","Ui/SimpleDropdown"],function(e,t,i,n,o,r){"use strict";var a=new t,s=new t,l=new t,c=new t;return{setup:function(){null!==elById("mainMenu")&&this.register("mainMenu");var e=elBySel(".navigationHeader");null!==e&&this.register(o.identify(e)),window.addEventListener("resize",this.rebuildAll.bind(this)),i.add("WoltLabSuite/Core/Ui/FlexibleMenu",this.registerTabMenus.bind(this))},register:function(e){var t=elById(e);if(null===t)throw"Expected a valid element id, '"+e+"' does not exist.";if(!a.has(e)){var i=n.childByTag(t,"UL");if(null===i)throw"Expected an <ul> element as child of container '"+e+"'.";a.set(e,t),c.set(e,i),this.rebuild(e)}},registerTabMenus:function(){for(var e=elBySelAll(".tabMenuContainer:not(.jsFlexibleMenuEnabled), .messageTabMenu:not(.jsFlexibleMenuEnabled)"),t=0,i=e.length;t<i;t++){var r=e[t],a=n.childByTag(r,"NAV");null!==a&&(r.classList.add("jsFlexibleMenuEnabled"),this.register(o.identify(a)))}},rebuildAll:function(){a.forEach(function(e,t){this.rebuild(t)}.bind(this))},rebuild:function(t){var i=a.get(t);if(void 0===i)throw"Expected a valid element id, '"+t+"' is unknown.";var u=window.getComputedStyle(i),d=i.parentNode.clientWidth;d-=o.styleAsInt(u,"margin-left"),d-=o.styleAsInt(u,"margin-right");var h=c.get(t),f=n.childrenByTag(h,"LI"),p=s.get(t),g=0;if(void 0!==p){for(var m=0,v=f.length;m<v;m++){var b=f[m];b.classList.contains("dropdown")||elShow(b)}null!==p.parentNode&&(g=o.outerWidth(p))}var _=h.scrollWidth-g,y=[];if(_>d)for(var m=f.length-1;m>=0;m--){var b=f[m];if(!(b.classList.contains("dropdown")||b.classList.contains("active")||b.classList.contains("ui-state-active"))&&(y.push(b),elHide(b),h.scrollWidth<d))break}if(y.length){var w;if(void 0===p){p=elCreate("li"),p.className="dropdown jsFlexibleMenuDropdown";var C=elCreate("a");C.className="icon icon16 fa-list",p.appendChild(C),w=elCreate("ul"),w.classList.add("dropdownMenu"),p.appendChild(w),s.set(t,p),l.set(t,w),r.init(C)}else w=l.get(t);null===p.parentNode&&h.appendChild(p);var E=document.createDocumentFragment(),L=this;y.forEach(function(i){var n=elCreate("li");n.innerHTML=i.innerHTML,n.addEventListener(WCF_CLICK_EVENT,function(n){n.preventDefault(),e.triggerEvent(elBySel("a",i),WCF_CLICK_EVENT),setTimeout(function(){L.rebuild(t)},59)}.bind(this)),E.appendChild(n)}),w.innerHTML="",w.appendChild(E)}else void 0!==p&&null!==p.parentNode&&elRemove(p)}}}),define("WoltLabSuite/Core/Ui/Tooltip",["Environment","Dom/ChangeListener","Ui/Alignment"],function(e,t,i){"use strict";var n=null,o=null,r=null,a=null,s=null,l=null;return{setup:function(){"desktop"===e.platform()&&(l=elCreate("div"),elAttr(l,"id","balloonTooltip"),l.classList.add("balloonTooltip"),l.addEventListener("transitionend",function(){l.classList.contains("active")||["bottom","left","right","top"].forEach(function(e){l.style.removeProperty(e)})}),s=elCreate("span"),elAttr(s,"id","balloonTooltipText"),l.appendChild(s),a=elCreate("span"),a.classList.add("elementPointer"),a.appendChild(elCreate("span")),l.appendChild(a),document.body.appendChild(l),r=elByClass("jsTooltip"),n=this._mouseEnter.bind(this),o=this._mouseLeave.bind(this),this.init(),t.add("WoltLabSuite/Core/Ui/Tooltip",this.init.bind(this)),window.addEventListener("scroll",this._mouseLeave.bind(this)))},init:function(){0!==r.length&&elBySelAll(".jsTooltip",void 0,function(e){e.classList.remove("jsTooltip");var t=elAttr(e,"title").trim();t.length&&(elData(e,"tooltip",t),e.removeAttribute("title"),elAttr(e,"aria-label",t),e.addEventListener("mouseenter",n),e.addEventListener("mouseleave",o),e.addEventListener(WCF_CLICK_EVENT,o))})},_mouseEnter:function(e){var t=e.currentTarget,n=elAttr(t,"title");if(n="string"==typeof n?n.trim():"",""!==n&&(elData(t,"tooltip",n),t.removeAttribute("title")),n=elData(t,"tooltip"),l.style.removeProperty("top"),l.style.removeProperty("left"),!n.length)return void l.classList.remove("active");l.classList.add("active"),s.textContent=n,i.set(l,t,{horizontal:"center",verticalOffset:4,pointer:!0,pointerClassNames:["inverse"],vertical:"top"})},_mouseLeave:function(){l.classList.remove("active")}}}),define("WoltLabSuite/Core/Date/Picker",["DateUtil","Dom/Traverse","Dom/Util","EventHandler","Language","ObjectMap","Dom/ChangeListener","Ui/Alignment","WoltLabSuite/Core/Ui/CloseOverlay"],function(e,t,i,n,o,r,a,s,l){"use strict";var c=!1,u=0,d=!1,h=new r,f=null,p=0,g=0,m=[],v=null,b=null,_=null,y=null,w=null,C=null,E=null,L=null,A=null,S=null,I=null,x={init:function(){this._setup();for(var t=elBySelAll('input[type="date"]:not(.inputDatePicker), input[type="datetime"]:not(.inputDatePicker)'),i=new Date,n=0,r=t.length;n<r;n++){var a=t[n];a.classList.add("inputDatePicker"),a.readOnly=!0;var s="datetime"===elAttr(a,"type"),l=s&&elDataBool(a,"time-only"),c=elDataBool(a,"disable-clear"),u=s&&elDataBool(a,"ignore-timezone"),d=a.classList.contains("birthday");elData(a,"is-date-time",s),elData(a,"is-time-only",l);var f=null,p=elAttr(a,"value"),g=/^\d+-\d+-\d+$/.test(p);if(elAttr(a,"value")){if(l){f=new Date;var m=p.split(":");f.setHours(m[0],m[1])}else{if(u||d||g){var v=new Date(p).getTimezoneOffset(),b=v>0?"-":"+";v=Math.abs(v);var _=Math.floor(v/60).toString(),y=(v%60).toString();b+=2===_.length?_:"0"+_,b+=":",b+=2===y.length?y:"0"+y,d||g?p+="T00:00:00"+b:p=p.replace(/[+-][0-9]{2}:[0-9]{2}$/,b)}f=new Date(p)}var w=f.getTime();if(isNaN(w))p="";else{elData(a,"value",w);p=e[l?"formatTime":"formatDate"+(s?"Time":"")](f)}}var C=0===p.length;if(d?(elData(a,"min-date","120"),elData(a,"max-date",(new Date).getFullYear()+"-12-31")):(a.min&&elData(a,"min-date",a.min),a.max&&elData(a,"max-date",a.max)),this._initDateRange(a,i,!0),this._initDateRange(a,i,!1),elData(a,"min-date")===elData(a,"max-date"))throw new Error("Minimum and maximum date cannot be the same (element id '"+a.id+"').");a.type="text",a.value=p,elData(a,"empty",C),elData(a,"placeholder")&&elAttr(a,"placeholder",elData(a,"placeholder"));var E=elCreate("input");if(E.id=a.id+"DatePicker",E.name=a.name,E.type="hidden",null!==f&&(E.value=l?e.format(f,"H:i"):u?e.format(f,"Y-m-dTH:i:s"):e.format(f,s?"c":"Y-m-d")),a.parentNode.insertBefore(E,a),a.removeAttribute("name"),a.addEventListener(WCF_CLICK_EVENT,S),!a.disabled){var L=elCreate("div");L.className="inputAddon";var A=elCreate("a");A.className="inputSuffix button jsTooltip",A.href="#",elAttr(A,"role","button"),elAttr(A,"tabindex","0"),elAttr(A,"title",o.get("wcf.date.datePicker")),elAttr(A,"aria-label",o.get("wcf.date.datePicker")),elAttr(A,"aria-haspopup",!0),elAttr(A,"aria-expanded",!1),A.addEventListener(WCF_CLICK_EVENT,S),L.appendChild(A);var I=elCreate("span");I.className="icon icon16 fa-calendar",A.appendChild(I),a.parentNode.insertBefore(L,a),L.insertBefore(a,A),c||(A=elCreate("a"),A.className="inputSuffix button",A.addEventListener(WCF_CLICK_EVENT,this.clear.bind(this,a)),C&&A.style.setProperty("visibility","hidden",""),L.appendChild(A),I=elCreate("span"),I.className="icon icon16 fa-times",A.appendChild(I))}for(var x=!1,D=["tiny","short","medium","long"],k=0;k<4;k++)a.classList.contains(D[k])&&(x=!0);x||a.classList.add("short"),h.set(a,{clearButton:A,shadow:E,disableClear:c,isDateTime:s,isEmpty:C,isTimeOnly:l,ignoreTimezone:u,onClose:null})}},_initDateRange:function(e,t,i){var n="data-"+(i?"min":"max")+"-date",o=e.hasAttribute(n)?elAttr(e,n).trim():"";if(o.match(/^(\d{4})-(\d{2})-(\d{2})$/))o=new Date(o).getTime();else if("now"===o)o=t.getTime();else if(o.match(/^\d{1,3}$/)){var r=new Date(t.getTime());r.setFullYear(r.getFullYear()+~~o*(i?-1:1)),o=r.getTime()}else if(o.match(/^datePicker-(.+)$/)){if(o=RegExp.$1,null===elById(o))throw new Error("Reference date picker identified by '"+o+"' does not exists (element id: '"+e.id+"').")}else o=/^\d{4}\-\d{2}\-\d{2}T/.test(o)?new Date(o).getTime():new Date(i?1902:2038,0,1).getTime();elAttr(e,n,o)},_setup:function(){c||(c=!0,u=~~o.get("wcf.date.firstDayOfTheWeek"),S=this._open.bind(this),a.add("WoltLabSuite/Core/Date/Picker",this.init.bind(this)),l.add("WoltLabSuite/Core/Date/Picker",this._close.bind(this)))},_open:function(e){e.preventDefault(),e.stopPropagation(),this._createPicker(),null===I&&(I=this._maintainFocus.bind(this),document.body.addEventListener("focus",I,{capture:!0}));var i="INPUT"===e.currentTarget.nodeName?e.currentTarget:e.currentTarget.previousElementSibling;if(i===f)return void this._close();var n=t.parentByClass(i,"dialogContent");null!==n&&(elDataBool(n,"has-datepicker-scroll-listener")||(n.addEventListener("scroll",this._onDialogScroll.bind(this)),elData(n,"has-datepicker-scroll-listener",1))),f=i;var o,r=h.get(f),a=elData(f,"value");a?(o=new Date(+a),"Invalid Date"===o.toString()&&(o=new Date)):o=new Date,g=elData(f,"min-date"),g.match(/^datePicker-(.+)$/)&&(g=elData(elById(RegExp.$1),"value")),g=new Date(+g),p=elData(f,"max-date"),p.match(/^datePicker-(.+)$/)&&(p=elData(elById(RegExp.$1),"value")),p=new Date(+p),r.isDateTime?(b.value=o.getHours(),_.value=o.getMinutes(),A.classList.add("datePickerTime")):A.classList.remove("datePickerTime"),A.classList[r.isTimeOnly?"add":"remove"]("datePickerTimeOnly"),this._renderPicker(o.getDate(),o.getMonth(),o.getFullYear()),s.set(A,f),elAttr(f.nextElementSibling,"aria-expanded",!0),d=!1},_close:function(){if(null!==A&&A.classList.contains("active")){A.classList.remove("active");var e=h.get(f);"function"==typeof e.onClose&&e.onClose(),n.fire("WoltLabSuite/Core/Date/Picker","close",{element:f}),elAttr(f.nextElementSibling,"aria-expanded",!1),f=null,g=0,p=0}},_onDialogScroll:function(e){if(null!==f){var t=e.currentTarget,n=i.offset(f),o=i.offset(t);n.top+f.clientHeight<=o.top?this._close():n.top>=o.top+t.offsetHeight?this._close():n.left<=o.left?this._close():n.left>=o.left+t.offsetWidth?this._close():s.set(A,f)}},_renderPicker:function(e,t,i){
+this._renderGrid(e,t,i);for(var n="",o=g.getFullYear(),r=p.getFullYear();o<=r;o++)n+='<option value="'+o+'">'+o+"</option>";L.innerHTML=n,L.value=i,y.value=t,A.classList.add("active")},_renderGrid:function(t,i,n){var o,r,a=void 0!==t,s=void 0!==i;if(t=~~t||~~elData(v,"day"),i=~~i,n=~~n,s||n){var l=0!==n,c=document.createDocumentFragment();c.appendChild(v),s||(i=~~elData(v,"month")),n=n||~~elData(v,"year");var d=new Date(n+"-"+("0"+(i+1).toString()).slice(-2)+"-"+("0"+t.toString()).slice(-2));for(d<g?(n=g.getFullYear(),i=g.getMonth(),t=g.getDate(),y.value=i,L.value=n,l=!0):d>p&&(n=p.getFullYear(),i=p.getMonth(),t=p.getDate(),y.value=i,L.value=n,l=!0),d=new Date(n+"-"+("0"+(i+1).toString()).slice(-2)+"-01");d.getDay()!==u;)d.setDate(d.getDate()-1);elShow(m[35].parentNode);var h,f=new Date(g.getFullYear(),g.getMonth(),g.getDate());for(r=0;r<42;r++){if(35===r&&d.getMonth()!==i){elHide(m[35].parentNode);break}o=m[r],o.textContent=d.getDate(),h=d.getMonth()===i,h&&(d<f?h=!1:d>p&&(h=!1)),o.classList[h?"remove":"add"]("otherMonth"),h&&(o.href="#",elAttr(o,"role","button"),elAttr(o,"tabindex","0"),elAttr(o,"title",e.formatDate(d)),elAttr(o,"aria-label",e.formatDate(d))),d.setDate(d.getDate()+1)}if(elData(v,"month",i),elData(v,"year",n),A.insertBefore(c,E),!a&&(d=new Date(n,i,t),d.getDate()!==t)){for(;d.getMonth()!==i;)d.setDate(d.getDate()-1);t=d.getDate()}if(l){for(r=0;r<12;r++){var b=y.children[r];b.disabled=n===g.getFullYear()&&b.value<g.getMonth()||n===p.getFullYear()&&b.value>p.getMonth()}var _=new Date(n+"-"+("0"+(i+1).toString()).slice(-2)+"-01");_.setMonth(_.getMonth()+1),w.classList[_<p?"add":"remove"]("active");var S=new Date(n+"-"+("0"+(i+1).toString()).slice(-2)+"-01");S.setDate(S.getDate()-1),C.classList[S>g?"add":"remove"]("active")}}if(t){for(r=0;r<35;r++)o=m[r],o.classList[o.classList.contains("otherMonth")||~~o.textContent!==t?"remove":"add"]("active");elData(v,"day",t)}this._formatValue()},_formatValue:function(){var e,t=h.get(f);"true"!==elData(f,"empty")&&(e=t.isDateTime?new Date(elData(v,"year"),elData(v,"month"),elData(v,"day"),b.value,_.value):new Date(elData(v,"year"),elData(v,"month"),elData(v,"day")),this.setDate(f,e))},_createPicker:function(){if(null===A){A=elCreate("div"),A.className="datePicker",A.addEventListener(WCF_CLICK_EVENT,function(e){e.stopPropagation()});var t=elCreate("header");A.appendChild(t),C=elCreate("a"),C.className="previous jsTooltip",C.href="#",elAttr(C,"role","button"),elAttr(C,"tabindex","0"),elAttr(C,"title",o.get("wcf.date.datePicker.previousMonth")),elAttr(C,"aria-label",o.get("wcf.date.datePicker.previousMonth")),C.innerHTML='<span class="icon icon16 fa-arrow-left"></span>',C.addEventListener(WCF_CLICK_EVENT,this.previousMonth.bind(this)),t.appendChild(C);var i=elCreate("span");t.appendChild(i),y=elCreate("select"),y.className="month jsTooltip",elAttr(y,"title",o.get("wcf.date.datePicker.month")),elAttr(y,"aria-label",o.get("wcf.date.datePicker.month")),y.addEventListener("change",this._changeMonth.bind(this)),i.appendChild(y);var n,r="",a=o.get("__monthsShort");for(n=0;n<12;n++)r+='<option value="'+n+'">'+a[n]+"</option>";y.innerHTML=r,L=elCreate("select"),L.className="year jsTooltip",elAttr(L,"title",o.get("wcf.date.datePicker.year")),elAttr(L,"aria-label",o.get("wcf.date.datePicker.year")),L.addEventListener("change",this._changeYear.bind(this)),i.appendChild(L),w=elCreate("a"),w.className="next jsTooltip",w.href="#",elAttr(w,"role","button"),elAttr(w,"tabindex","0"),elAttr(w,"title",o.get("wcf.date.datePicker.nextMonth")),elAttr(w,"aria-label",o.get("wcf.date.datePicker.nextMonth")),w.innerHTML='<span class="icon icon16 fa-arrow-right"></span>',w.addEventListener(WCF_CLICK_EVENT,this.nextMonth.bind(this)),t.appendChild(w),v=elCreate("ul"),A.appendChild(v);var s=elCreate("li");s.className="weekdays",v.appendChild(s);var l,c=o.get("__daysShort");for(n=0;n<7;n++){var d=n+u;d>6&&(d-=7),l=elCreate("span"),l.textContent=c[d],s.appendChild(l)}var h,f,p=this._click.bind(this);for(n=0;n<6;n++){f=elCreate("li"),v.appendChild(f);for(var g=0;g<7;g++)h=elCreate("a"),h.addEventListener(WCF_CLICK_EVENT,p),m.push(h),f.appendChild(h)}E=elCreate("footer"),A.appendChild(E),b=elCreate("select"),b.className="hour",elAttr(b,"title",o.get("wcf.date.datePicker.hour")),elAttr(b,"aria-label",o.get("wcf.date.datePicker.hour")),b.addEventListener("change",this._formatValue.bind(this));var S="",I=new Date(2e3,0,1),x=o.get("wcf.date.timeFormat").replace(/:/,"").replace(/[isu]/g,"");for(n=0;n<24;n++)I.setHours(n),S+='<option value="'+n+'">'+e.format(I,x)+"</option>";for(b.innerHTML=S,E.appendChild(b),E.appendChild(document.createTextNode(" : ")),_=elCreate("select"),_.className="minute",elAttr(_,"title",o.get("wcf.date.datePicker.minute")),elAttr(_,"aria-label",o.get("wcf.date.datePicker.minute")),_.addEventListener("change",this._formatValue.bind(this)),S="",n=0;n<60;n++)S+='<option value="'+n+'">'+(n<10?"0"+n.toString():n)+"</option>";_.innerHTML=S,E.appendChild(_),document.body.appendChild(A)}},previousMonth:function(e){e.preventDefault(),"0"===y.value?(y.value=11,L.value=~~L.value-1):y.value=~~y.value-1,this._renderGrid(void 0,y.value,L.value)},nextMonth:function(e){e.preventDefault(),"11"===y.value?(y.value=0,L.value=1+~~L.value):y.value=1+~~y.value,this._renderGrid(void 0,y.value,L.value)},_changeMonth:function(e){this._renderGrid(void 0,e.currentTarget.value)},_changeYear:function(e){this._renderGrid(void 0,void 0,e.currentTarget.value)},_click:function(e){if(e.preventDefault(),!e.currentTarget.classList.contains("otherMonth")){elData(f,"empty",!1),this._renderGrid(e.currentTarget.textContent);h.get(f).isDateTime||this._close()}},getDate:function(e){return e=this._getElement(e),e.hasAttribute("data-value")?new Date(+elData(e,"value")):null},setDate:function(t,i){t=this._getElement(t);var n=h.get(t);elData(t,"value",i.getTime());var o,r="";n.isDateTime?n.isTimeOnly?(o=e.formatTime(i),r="H:i"):n.ignoreTimezone?(o=e.formatDateTime(i),r="Y-m-dTH:i:s"):(o=e.formatDateTime(i),r="c"):(o=e.formatDate(i),r="Y-m-d"),t.value=o,n.shadow.value=e.format(i,r),n.disableClear||n.clearButton.style.removeProperty("visibility")},getValue:function(e){e=this._getElement(e);var t=h.get(e);return t?t.shadow.value:""},clear:function(e){e=this._getElement(e);var t=h.get(e);e.removeAttribute("data-value"),e.value="",t.disableClear||t.clearButton.style.setProperty("visibility","hidden",""),t.isEmpty=!0,t.shadow.value=""},destroy:function(e){e=this._getElement(e);var t=h.get(e),i=e.parentNode;i.parentNode.insertBefore(e,i),elRemove(i),elAttr(e,"type","date"+(t.isDateTime?"time":"")),e.name=t.shadow.name,e.value=t.shadow.value,e.removeAttribute("data-value"),e.removeEventListener(WCF_CLICK_EVENT,S),elRemove(t.shadow),e.classList.remove("inputDatePicker"),e.readOnly=!1,h.delete(e)},setCloseCallback:function(e,t){e=this._getElement(e),h.get(e).onClose=t},_getElement:function(e){if("string"==typeof e&&(e=elById(e)),!(e instanceof Element&&e.classList.contains("inputDatePicker")&&h.has(e)))throw new Error("Expected a valid date picker input element or id.");return e},_maintainFocus:function(e){null!==A&&A.classList.contains("active")&&(A.contains(e.target)?d=!0:d?(f.nextElementSibling.focus(),d=!1):elBySel(".previous",A).focus())}};return window.__wcf_bc_datePicker=x,x}),define("WoltLabSuite/Core/Ui/Page/Action",["Dictionary","Dom/Util"],function(e,t){"use strict";var i=new e,n=null,o=!1;return{setup:function(){o=!0,n=elCreate("ul"),n.className="pageAction",document.body.appendChild(n)},add:function(e,r,a){!1===o&&this.setup();var s=elCreate("li");if(r.classList.add("button"),r.classList.add("buttonPrimary"),s.appendChild(r),elAttr(s,"aria-hidden","toTop"===e?"true":"false"),elData(s,"name",e),"toTop"===e)s.className="toTop initiallyHidden",n.appendChild(s);else{var l=null;a&&void 0!==(l=i.get(a))&&(l=l.parentNode),null===l&&n.childElementCount&&(l=n.children[0]),null===l?t.prepend(s,n):n.insertBefore(s,l)}i.set(e,r),this._renderContainer()},has:function(e){return i.has(e)},get:function(e){return i.get(e)},remove:function(e){var t=i.get(e);if(void 0!==t){var o=t.parentNode;o.addEventListener("animationend",function(){try{n.removeChild(o),i.delete(e)}catch(e){}}),this.hide(e)}},hide:function(e){var t=i.get(e);t&&(elAttr(t.parentNode,"aria-hidden","true"),this._renderContainer())},show:function(e){var t=i.get(e);t&&(t.parentNode.classList.contains("initiallyHidden")&&t.parentNode.classList.remove("initiallyHidden"),elAttr(t.parentNode,"aria-hidden","false"),this._renderContainer())},_renderContainer:function(){var e=!1;if(n.childElementCount)for(var t=0,i=n.childElementCount;t<i;t++)if("false"===elAttr(n.children[t],"aria-hidden")){e=!0;break}n.classList[e?"add":"remove"]("active")}}}),define("WoltLabSuite/Core/Ui/Page/JumpToTop",["Environment","Language","./Action"],function(e,t,i){"use strict";function n(){this.init()}return n.prototype={init:function(){if("desktop"===e.platform()){this._callbackScrollEnd=this._afterScroll.bind(this),this._timeoutScroll=null;var n=elCreate("a");n.className="jsTooltip",n.href="#",elAttr(n,"title",t.get("wcf.global.scrollUp")),elAttr(n,"role","button"),n.innerHTML='<span class="icon icon32 fa-angle-up"></span>',n.addEventListener(WCF_CLICK_EVENT,this._jump.bind(this)),i.add("toTop",n),window.addEventListener("scroll",this._scroll.bind(this)),this._afterScroll()}},_jump:function(e){e.preventDefault(),elById("top").scrollIntoView({behavior:"smooth"})},_scroll:function(){null!==this._timeoutScroll&&window.clearTimeout(this._timeoutScroll),this._timeoutScroll=window.setTimeout(this._callbackScrollEnd,100)},_afterScroll:function(){this._timeoutScroll=null,i[window.pageYOffset>=300?"show":"hide"]("toTop")}},n}),define("WoltLabSuite/Core/Bootstrap",["favico","enquire","perfect-scrollbar","WoltLabSuite/Core/Date/Time/Relative","Ui/SimpleDropdown","WoltLabSuite/Core/Ui/Mobile","WoltLabSuite/Core/Ui/TabMenu","WoltLabSuite/Core/Ui/FlexibleMenu","Ui/Dialog","WoltLabSuite/Core/Ui/Tooltip","WoltLabSuite/Core/Language","WoltLabSuite/Core/Environment","WoltLabSuite/Core/Date/Picker","EventHandler","Core","WoltLabSuite/Core/Ui/Page/JumpToTop","Devtools","Dom/ChangeListener"],function(e,t,i,n,o,r,a,s,l,c,u,d,h,f,p,g,m,v){"use strict";return window.Favico=e,window.enquire=t,null==window.WCF&&(window.WCF={}),null==window.WCF.Language&&(window.WCF.Language={}),window.WCF.Language.get=u.get,window.WCF.Language.add=u.add,window.WCF.Language.addObject=u.addObject,window.__wcf_bc_eventHandler=f,{setup:function(e){e=p.extend({enableMobileMenu:!0},e),window.ENABLE_DEVELOPER_TOOLS&&m._internal_.enable(),d.setup(),n.setup(),h.init(),o.setup(),r.setup({enableMobileMenu:e.enableMobileMenu}),a.setup(),l.setup(),c.setup();for(var t=elBySelAll("form[method=get]"),i=0,s=t.length;i<s;i++)t[i].setAttribute("method","post");"microsoft"===d.browser()&&(window.onbeforeunload=function(){});var u=0;u=window.setInterval(function(){"function"==typeof window.jQuery&&(window.clearInterval(u),window.jQuery(function(){new g}),window.jQuery.holdReady(!1))},20),this._initA11y(),v.add("WoltLabSuite/Core/Bootstrap",this._initA11y.bind(this))},_initA11y:function(){elBySelAll("nav:not([aria-label]):not([aria-labelledby]):not([role])",void 0,function(e){elAttr(e,"role","presentation")}),elBySelAll("article:not([aria-label]):not([aria-labelledby]):not([role])",void 0,function(e){elAttr(e,"role","presentation")})}}}),define("WoltLabSuite/Core/Controller/Style/Changer",["Ajax","Language","Ui/Dialog"],function(e,t,i){"use strict";return{setup:function(){var e=elBySel(".jsButtonStyleChanger");e&&e.addEventListener(WCF_CLICK_EVENT,this.showDialog.bind(this))},showDialog:function(e){e.preventDefault(),i.open(this)},_dialogSetup:function(){return{id:"styleChanger",options:{disableContentPadding:!0,title:t.get("wcf.style.changeStyle")},source:{data:{actionName:"getStyleChooser",className:"wcf\\data\\style\\StyleAction"},after:function(e){for(var t=elBySelAll(".styleList > li",e),i=0,n=t.length;i<n;i++){var o=t[i];o.classList.add("pointer"),o.addEventListener(WCF_CLICK_EVENT,this._click.bind(this))}}.bind(this)}}},_click:function(t){t.preventDefault(),e.apiOnce({data:{actionName:"changeStyle",className:"wcf\\data\\style\\StyleAction",objectIDs:[elData(t.currentTarget,"style-id")]},success:function(){window.location.reload()}})}}}),define("WoltLabSuite/Core/Controller/Popover",["Ajax","Dictionary","Environment","Dom/ChangeListener","Dom/Util","Ui/Alignment"],function(e,t,i,n,o,r){"use strict";var a=null,s=new t,l=new t,c=new t,u=null,d=!1,h=null,f=null,p=null,g=null,m=null,v=null,b=null,_=null;return{_setup:function(){if(null===p){p=elCreate("div"),p.className="popover forceHide",g=elCreate("div"),g.className="popoverContent",p.appendChild(g);var e=elCreate("span");e.className="elementPointer",e.appendChild(elCreate("span")),p.appendChild(e),document.body.appendChild(p),m=this._hide.bind(this),b=this._mouseEnter.bind(this),_=this._mouseLeave.bind(this),p.addEventListener("mouseenter",this._popoverMouseEnter.bind(this)),p.addEventListener("mouseleave",_),p.addEventListener("animationend",this._clearContent.bind(this)),window.addEventListener("beforeunload",function(){d=!0,null!==h&&window.clearTimeout(h),this._hide(!0)}.bind(this)),n.add("WoltLabSuite/Core/Controller/Popover",this._init.bind(this))}},init:function(e){"desktop"===i.platform()&&(e.attributeName=e.attributeName||"data-object-id",e.legacy=!0===e.legacy,this._setup(),c.has(e.identifier)||(c.set(e.identifier,{attributeName:e.attributeName,elements:e.legacy?e.className:elByClass(e.className),legacy:e.legacy,loadCallback:e.loadCallback}),this._init(e.identifier)))},_init:function(e){"string"==typeof e&&e.length?this._initElements(c.get(e),e):c.forEach(this._initElements.bind(this))},_initElements:function(e,t){for(var i=e.legacy?elBySelAll(e.elements):e.elements,n=0,r=i.length;n<r;n++){var a=i[n],c=o.identify(a);if(s.has(c))return;if(null!==a.closest(".popover"))return void s.set(c,{content:null,state:0});var u=e.legacy?c:~~a.getAttribute(e.attributeName);if(0!==u){a.addEventListener("mouseenter",b),a.addEventListener("mouseleave",_),"A"===a.nodeName&&elAttr(a,"href")&&a.addEventListener(WCF_CLICK_EVENT,m);var d=t+"-"+u;elData(a,"cache-id",d),l.set(c,{element:a,identifier:t,objectId:u}),s.has(d)||s.set(t+"-"+u,{content:null,state:0})}}},setContent:function(e,t,i){var n=e+"-"+t,r=s.get(n);if(void 0===r)throw new Error("Unable to find element for object id '"+t+"' (identifier: '"+e+"').");var c=o.createFragmentFromHtml(i);if(c.childElementCount||(c=o.createFragmentFromHtml("<p>"+i+"</p>")),r.content=c,r.state=2,a){var u=l.get(a).element;elData(u,"cache-id")===n&&this._show()}},_mouseEnter:function(e){if(!d){null!==h&&(window.clearTimeout(h),h=null);var t=o.identify(e.currentTarget);a===t&&null!==f&&(window.clearTimeout(f),f=null),u=t,h=window.setTimeout(function(){h=null,u===t&&this._show()}.bind(this),800)}},_mouseLeave:function(){u=null,null===f&&(null===v&&(v=this._hide.bind(this)),null!==f&&window.clearTimeout(f),f=window.setTimeout(v,500))},_popoverMouseEnter:function(){null!==f&&(window.clearTimeout(f),f=null)},_show:function(){null!==f&&(window.clearTimeout(f),f=null);var e=!1;p.classList.contains("active")?a!==u&&(this._hide(),e=!0):g.childElementCount&&(e=!0),e&&(p.classList.add("forceHide"),p.offsetTop,this._clearContent(),p.classList.remove("forceHide")),a=u;var t=l.get(a);if(void 0!==t){var i=s.get(elData(t.element,"cache-id"));2===i.state?(g.appendChild(i.content),this._rebuild(a)):0===i.state&&(i.state=1,c.get(t.identifier).loadCallback(t.objectId,this))}},_hide:function(){null!==f&&(window.clearTimeout(f),f=null),p.classList.remove("active")},_clearContent:function(){if(a&&g.childElementCount&&!p.classList.contains("active"))for(var e=s.get(elData(l.get(a).element,"cache-id"));g.childNodes.length;)e.content.appendChild(g.childNodes[0])},_rebuild:function(){p.classList.contains("active")||(p.classList.remove("forceHide"),p.classList.add("active"),r.set(p,l.get(a).element,{pointer:!0,vertical:"top"}))},_ajaxSetup:function(){return{silent:!0}},ajaxApi:function(t,i,n){if("function"!=typeof i)throw new TypeError("Expected a valid callback for parameter 'success'.");e.api(this,t,i,n)}}}),define("WoltLabSuite/Core/Ui/User/Ignore",["List","Dom/ChangeListener"],function(e,t){"use strict";var i=function(){};return i.prototype={init:function(){},_rebuild:function(){},_removeClass:function(){}},i}),define("WoltLabSuite/Core/Ui/Page/Header/Menu",["Environment","Language","Ui/Screen"],function(e,t,i){"use strict";var n,o,r,a,s=!1,l=0,c=[],u=[];return{init:function(){if(a=elBySel(".mainMenu .boxMenu"),null===(r=a&&a.childElementCount?a.children[0]:null))throw new Error("Unable to find the menu.");i.on("screen-lg",{enable:this._enable.bind(this),disable:this._disable.bind(this),setup:this._setup.bind(this)})},_enable:function(){s=!0,"safari"===e.browser()?window.setTimeout(this._rebuildVisibility.bind(this),1e3):(this._rebuildVisibility(),window.setTimeout(this._rebuildVisibility.bind(this),1e3))},_disable:function(){s=!1},_showNext:function(e){if(e.preventDefault(),u.length){var t=u.slice(0,3).pop();this._setMarginLeft(a.clientWidth-(t.offsetLeft+t.clientWidth)),a.lastElementChild===t&&n.classList.remove("active"),o.classList.add("active")}},_showPrevious:function(e){if(e.preventDefault(),c.length){var t=c.slice(-3)[0];this._setMarginLeft(-1*t.offsetLeft),a.firstElementChild===t&&o.classList.remove("active"),n.classList.add("active")}},_setMarginLeft:function(e){l=Math.min(l+e,0),r.style.setProperty("margin-left",l+"px","")},_rebuildVisibility:function(){if(s){c=[],u=[];var e=a.clientWidth;if(a.scrollWidth>e||l<0)for(var t,i=0,r=a.childElementCount;i<r;i++){t=a.children[i];var d=t.offsetLeft;d<0?c.push(t):d+t.clientWidth>e&&u.push(t)}o.classList[c.length?"add":"remove"]("active"),n.classList[u.length?"add":"remove"]("active")}},_setup:function(){this._setupOverflow(),this._setupA11y()},_setupOverflow:function(){n=elCreate("a"),n.className="mainMenuShowNext",n.href="#",n.innerHTML='<span class="icon icon32 fa-angle-right"></span>',n.addEventListener(WCF_CLICK_EVENT,this._showNext.bind(this)),a.parentNode.appendChild(n),o=elCreate("a"),o.className="mainMenuShowPrevious",o.href="#",o.innerHTML='<span class="icon icon32 fa-angle-left"></span>',o.addEventListener(WCF_CLICK_EVENT,this._showPrevious.bind(this)),a.parentNode.insertBefore(o,a.parentNode.firstChild);var e=this._rebuildVisibility.bind(this);r.addEventListener("transitionend",e),window.addEventListener("resize",function(){r.style.setProperty("margin-left","0px",""),l=0,e()}),this._enable()},_setupA11y:function(){elBySelAll(".boxMenuHasChildren",a,function(e){var i=!1,n=elBySel(".boxMenuLink",e);n&&(elAttr(n,"aria-haspopup",!0),elAttr(n,"aria-expanded",i));var o=elCreate("button");o.className="visuallyHidden",o.tabindex=0,elAttr(o,"role","button"),elAttr(o,"aria-label",t.get("wcf.global.button.showMenu")),e.insertBefore(o,n.nextSibling),o.addEventListener(WCF_CLICK_EVENT,function(){i=!i,elAttr(n,"aria-expanded",i),elAttr(o,"aria-label",i?t.get("wcf.global.button.hideMenu"):t.get("wcf.global.button.showMenu"))})}.bind(this))}}}),define("WoltLabSuite/Core/BootstrapFrontend",["WoltLabSuite/Core/BackgroundQueue","WoltLabSuite/Core/Bootstrap","WoltLabSuite/Core/Controller/Style/Changer","WoltLabSuite/Core/Controller/Popover","WoltLabSuite/Core/Ui/User/Ignore","WoltLabSuite/Core/Ui/Page/Header/Menu"],function(e,t,i,n,o,r){"use strict";return{setup:function(n){n.backgroundQueue.url=WSC_API_URL+n.backgroundQueue.url.substr(WCF_PATH.length),t.setup(),r.init(),n.styleChanger&&i.setup(),n.enableUserPopover&&this._initUserPopover(),e.setUrl(n.backgroundQueue.url),(Math.random()<.1||n.backgroundQueue.force)&&e.invoke()},_initUserPopover:function(){n.init({attributeName:"data-user-id",className:"userLink",identifier:"com.woltlab.wcf.user",loadCallback:function(e,t){var i=function(i){t.setContent("com.woltlab.wcf.user",e,i.returnValues.template)};t.ajaxApi({actionName:"getUserProfile",className:"wcf\\data\\user\\UserProfileAction",objectIDs:[e]},i,i)}})}}}),define("WoltLabSuite/Core/Clipboard",[],function(){"use strict";return{copyTextToClipboard:function(e){if(navigator.clipboard)return navigator.clipboard.writeText(e);if(window.getSelection){var t=elCreate("textarea");t.contentEditable=!0,t.readOnly=!1,t.style.cssText="position: absolute; left: -9999px; top: -9999px; width: 0; height: 0;",document.body.appendChild(t);try{t.value=e;var i=document.createRange();i.selectNodeContents(t);var n=window.getSelection();return n.removeAllRanges(),n.addRange(i),t.setSelectionRange(0,999999),document.execCommand("copy")?Promise.resolve():Promise.reject(new Error("execCommand('copy') failed"))}finally{elRemove(t)}}return Promise.reject(new Error("Neither navigator.clipboard, nor window.getSelection is supported."))},copyElementTextToClipboard:function(e){return this.copyTextToClipboard(e.textContent)}}}),define("WoltLabSuite/Core/ColorUtil",[],function(){"use strict";var e={hsvToRgb:function(e,t,i){var n,o,r,a,s,l={r:0,g:0,b:0};if(n=Math.floor(e/60),o=e/60-n,t/=100,i/=100,r=i*(1-t),a=i*(1-t*o),s=i*(1-t*(1-o)),0==t)l.r=l.g=l.b=i;else switch(n){case 1:l.r=a,l.g=i,l.b=r;break;case 2:l.r=r,l.g=i,l.b=s;break;case 3:l.r=r,l.g=a,l.b=i;break;case 4:l.r=s,l.g=r,l.b=i;break;case 5:l.r=i,l.g=r,l.b=a;break;case 0:case 6:l.r=i,l.g=s,l.b=r}return{r:Math.round(255*l.r),g:Math.round(255*l.g),b:Math.round(255*l.b)}},rgbToHsv:function(e,t,i){var n,o,r,a,s,l;if(e/=255,t/=255,i/=255,a=Math.max(Math.max(e,t),i),s=Math.min(Math.min(e,t),i),l=a-s,n=0,a!==s){switch(a){case e:n=(t-i)/l*60;break;case t:n=60*(2+(i-e)/l);break;case i:n=60*(4+(e-t)/l)}n<0&&(n+=360)}return o=0===a?0:l/a,r=a,{h:Math.round(n),s:Math.round(100*o),v:Math.round(100*r)}},hexToRgb:function(e){if(/^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(e)){var t=e.split("");return"#"===t[0]&&t.shift(),3===t.length?{r:parseInt(t[0]+""+t[0],16),g:parseInt(t[1]+""+t[1],16),b:parseInt(t[2]+""+t[2],16)}:{r:parseInt(t[0]+""+t[1],16),g:parseInt(t[2]+""+t[3],16),b:parseInt(t[4]+""+t[5],16)}}return Number.NaN},rgbToHex:function(e,t,i){var n="0123456789ABCDEF";return void 0===t&&e.toString().match(/^rgba?\((\d+), ?(\d+), ?(\d+)(?:, ?[0-9.]+)?\)$/)&&(e=RegExp.$1,t=RegExp.$2,i=RegExp.$3),n.charAt((e-e%16)/16)+""+n.charAt(e%16)+n.charAt((t-t%16)/16)+n.charAt(t%16)+n.charAt((i-i%16)/16)+n.charAt(i%16)}};return window.__wcf_bc_colorUtil=e,e}),define("WoltLabSuite/Core/FileUtil",["Dictionary","StringUtil"],function(e,t){"use strict";var i=e.fromObject({zip:"archive",rar:"archive",tar:"archive",gz:"archive",mp3:"audio",ogg:"audio",wav:"audio",php:"code",html:"code",htm:"code",tpl:"code",js:"code",xls:"excel",ods:"excel",xlsx:"excel",gif:"image",jpg:"image",jpeg:"image",png:"image",bmp:"image",webp:"image",avi:"video",wmv:"video",mov:"video",mp4:"video",mpg:"video",mpeg:"video",flv:"video",pdf:"pdf",ppt:"powerpoint",pptx:"powerpoint",txt:"text",doc:"word",docx:"word",odt:"word"}),n=e.fromObject({"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/mpeg":"mp3","audio/mp3":"mp3","audio/ogg":"ogg","audio/x-wav":"wav","application/x-php":"php","text/html":"html","application/javascript":"js","application/vnd.ms-excel":"xls","application/vnd.oasis.opendocument.spreadsheet":"ods","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":"xlsx","image/gif":"gif","image/jpeg":"jpg","image/png":"png","image/x-ms-bmp":"bmp","image/bmp":"bmp","image/webp":"webp","video/x-msvideo":"avi","video/x-ms-wmv":"wmv","video/quicktime":"mov","video/mp4":"mp4","video/mpeg":"mpg","video/x-flv":"flv","application/pdf":"pdf","application/vnd.ms-powerpoint":"ppt","application/vnd.openxmlformats-officedocument.presentationml.presentation":"pptx","text/plain":"txt","application/msword":"doc","application/vnd.openxmlformats-officedocument.wordprocessingml.document":"docx","application/vnd.oasis.opendocument.text":"odt"});return{formatFilesize:function(e,i){void 0===i&&(i=2);var n="Byte";return e>=1e3&&(e/=1e3,n="kB"),e>=1e3&&(e/=1e3,n="MB"),e>=1e3&&(e/=1e3,n="GB"),e>=1e3&&(e/=1e3,n="TB"),t.formatNumeric(e,-i)+" "+n},getIconNameByFilename:function(e){var t=e.lastIndexOf(".");if(!1!==t){var n=e.substr(t+1);if(i.has(n))return i.get(n)}return""},getExtensionByMimeType:function(e){return n.has(e)?"."+n.get(e):""},blobToFile:function(e,t){var i=this.getExtensionByMimeType(e.type),n=window.File;try{new n([],"ie11-check")}catch(e){n=function(e,t,i){var n=Blob.call(this,e,i);return n.name=t,n.lastModifiedDate=new Date,n},n.prototype=Object.create(window.File.prototype)}return new n([e],t+i,{type:e.type})}}}),define("WoltLabSuite/Core/Permission",["Dictionary"],function(e){"use strict";var t=new e;return{add:function(e,i){if("boolean"!=typeof i)throw new TypeError("Permission value has to be boolean.");t.set(e,i)},addObject:function(e){for(var t in e)objOwns(e,t)&&this.add(t,e[t])},get:function(e){return!!t.has(e)&&t.get(e)}}});var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-([\w-]+)\b/i,t=0,i=_self.Prism={manual:_self.Prism&&_self.Prism.manual,disableWorkerMessageHandler:_self.Prism&&_self.Prism.disableWorkerMessageHandler,util:{encode:function(e){return e instanceof n?new n(e.type,i.util.encode(e.content),e.alias):"Array"===i.util.type(e)?e.map(i.util.encode):e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).match(/\[object (\w+)\]/)[1]},objId:function(e){return e.__id||Object.defineProperty(e,"__id",{value:++t}),e.__id},clone:function(e,t){var n=i.util.type(e);switch(t=t||{},n){case"Object":if(t[i.util.objId(e)])return t[i.util.objId(e)];var o={};t[i.util.objId(e)]=o;for(var r in e)e.hasOwnProperty(r)&&(o[r]=i.util.clone(e[r],t));return o;case"Array":if(t[i.util.objId(e)])return t[i.util.objId(e)];var o=[];return t[i.util.objId(e)]=o,e.forEach(function(e,n){o[n]=i.util.clone(e,t)}),o}return e}},languages:{extend:function(e,t){var n=i.util.clone(i.languages[e]);for(var o in t)n[o]=t[o];return n},insertBefore:function(e,t,n,o){o=o||i.languages;var r=o[e];if(2==arguments.length){n=arguments[1];for(var a in n)n.hasOwnProperty(a)&&(r[a]=n[a]);return r}var s={};for(var l in r)if(r.hasOwnProperty(l)){if(l==t)for(var a in n)n.hasOwnProperty(a)&&(s[a]=n[a]);s[l]=r[l]}var c=o[e];return o[e]=s,i.languages.DFS(i.languages,function(t,i){i===c&&t!=e&&(this[t]=s)}),s},DFS:function(e,t,n,o){o=o||{};for(var r in e)e.hasOwnProperty(r)&&(t.call(e,r,e[r],n||r),"Object"!==i.util.type(e[r])||o[i.util.objId(e[r])]?"Array"!==i.util.type(e[r])||o[i.util.objId(e[r])]||(o[i.util.objId(e[r])]=!0,i.languages.DFS(e[r],t,r,o)):(o[i.util.objId(e[r])]=!0,i.languages.DFS(e[r],t,null,o)))}},plugins:{},highlightAll:function(e,t){i.highlightAllUnder(document,e,t)},highlightAllUnder:function(e,t,n){var o={callback:n,selector:'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'};i.hooks.run("before-highlightall",o);for(var r,a=o.elements||e.querySelectorAll(o.selector),s=0;r=a[s++];)i.highlightElement(r,!0===t,o.callback)},highlightElement:function(t,n,o){for(var r,a,s=t;s&&!e.test(s.className);)s=s.parentNode;s&&(r=(s.className.match(e)||[,""])[1].toLowerCase(),a=i.languages[r]),t.className=t.className.replace(e,"").replace(/\s+/g," ")+" language-"+r,t.parentNode&&(s=t.parentNode,/pre/i.test(s.nodeName)&&(s.className=s.className.replace(e,"").replace(/\s+/g," ")+" language-"+r));var l=t.textContent,c={element:t,language:r,grammar:a,code:l};if(i.hooks.run("before-sanity-check",c),!c.code||!c.grammar)return c.code&&(i.hooks.run("before-highlight",c),c.element.textContent=c.code,i.hooks.run("after-highlight",c)),void i.hooks.run("complete",c);if(i.hooks.run("before-highlight",c),n&&_self.Worker){var u=new Worker(i.filename);u.onmessage=function(e){c.highlightedCode=e.data,i.hooks.run("before-insert",c),c.element.innerHTML=c.highlightedCode,o&&o.call(c.element),i.hooks.run("after-highlight",c),i.hooks.run("complete",c)},u.postMessage(JSON.stringify({language:c.language,code:c.code,immediateClose:!0}))}else c.highlightedCode=i.highlight(c.code,c.grammar,c.language),i.hooks.run("before-insert",c),c.element.innerHTML=c.highlightedCode,o&&o.call(t),i.hooks.run("after-highlight",c),i.hooks.run("complete",c)},highlight:function(e,t,o){var r={code:e,grammar:t,language:o};return i.hooks.run("before-tokenize",r),r.tokens=i.tokenize(r.code,r.grammar),i.hooks.run("after-tokenize",r),n.stringify(i.util.encode(r.tokens),r.language)},matchGrammar:function(e,t,n,o,r,a,s){var l=i.Token;for(var c in n)if(n.hasOwnProperty(c)&&n[c]){if(c==s)return;var u=n[c];u="Array"===i.util.type(u)?u:[u];for(var d=0;d<u.length;++d){var h=u[d],f=h.inside,p=!!h.lookbehind,g=!!h.greedy,m=0,v=h.alias;if(g&&!h.pattern.global){var b=h.pattern.toString().match(/[imuy]*$/)[0];h.pattern=RegExp(h.pattern.source,b+"g")}h=h.pattern||h;for(var _=o,y=r;_<t.length;y+=t[_].length,++_){var w=t[_];if(t.length>e.length)return;if(!(w instanceof l)){if(g&&_!=t.length-1){h.lastIndex=y;var C=h.exec(e);if(!C)break;for(var E=C.index+(p?C[1].length:0),L=C.index+C[0].length,A=_,S=y,I=t.length;A<I&&(S<L||!t[A].type&&!t[A-1].greedy);++A)S+=t[A].length,E>=S&&(++_,y=S);if(t[_]instanceof l)continue;x=A-_,w=e.slice(y,S),C.index-=y}else{h.lastIndex=0;var C=h.exec(w),x=1}if(C){p&&(m=C[1]?C[1].length:0);var E=C.index+m,C=C[0].slice(m),L=E+C.length,D=w.slice(0,E),k=w.slice(L),T=[_,x];D&&(++_,y+=D.length,T.push(D));var B=new l(c,f?i.tokenize(C,f):C,v,C,g);if(T.push(B),k&&T.push(k),Array.prototype.splice.apply(t,T),1!=x&&i.matchGrammar(e,t,n,_,y,!0,c),a)break}else if(a)break}}}}},tokenize:function(e,t,n){var o=[e],r=t.rest;if(r){for(var a in r)t[a]=r[a];delete t.rest}return i.matchGrammar(e,o,t,0,0,!1),o},hooks:{all:{},add:function(e,t){var n=i.hooks.all;n[e]=n[e]||[],n[e].push(t)},run:function(e,t){var n=i.hooks.all[e];if(n&&n.length)for(var o,r=0;o=n[r++];)o(t)}}},n=i.Token=function(e,t,i,n,o){this.type=e,this.content=t,this.alias=i,this.length=0|(n||"").length,this.greedy=!!o};if(n.stringify=function(e,t,o){if("string"==typeof e)return e;if("Array"===i.util.type(e))return e.map(function(i){return n.stringify(i,t,e)}).join("");var r={type:e.type,content:n.stringify(e.content,t,o),tag:"span",classes:["token",e.type],attributes:{},language:t,parent:o};if(e.alias){var a="Array"===i.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(r.classes,a)}i.hooks.run("wrap",r);var s=Object.keys(r.attributes).map(function(e){return e+'="'+(r.attributes[e]||"").replace(/"/g,"&quot;")+'"'}).join(" ");return"<"+r.tag+' class="'+r.classes.join(" ")+'"'+(s?" "+s:"")+">"+r.content+"</"+r.tag+">"},!_self.document)return _self.addEventListener?(i.disableWorkerMessageHandler||_self.addEventListener("message",function(e){var t=JSON.parse(e.data),n=t.language,o=t.code,r=t.immediateClose;_self.postMessage(i.highlight(o,i.languages[n],n)),r&&_self.close()},!1),_self.Prism):_self.Prism;var o=document.currentScript||[].slice.call(document.getElementsByTagName("script")).pop();return o&&(i.filename=o.src,i.manual||o.hasAttribute("data-manual")||("loading"!==document.readyState?window.requestAnimationFrame?window.requestAnimationFrame(i.highlightAll):window.setTimeout(i.highlightAll,16):document.addEventListener("DOMContentLoaded",i.highlightAll))),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism),define("prism/prism",function(){}),window.Prism=window.Prism||{},window.Prism.manual=!0,define("WoltLabSuite/Core/Prism",["prism/prism"],function(){return Prism.wscSplitIntoLines=function(e){function t(){var e=elCreate("span");return elData(e,"number",a++),r.appendChild(e),e}
+var i,n,o,r=document.createDocumentFragment(),a=1;for(i=document.createNodeIterator(e,NodeFilter.SHOW_TEXT,function(){return NodeFilter.FILTER_ACCEPT},!1),o=t();n=i.nextNode();)n.data.split(/\r?\n/).forEach(function(i,r){var a,s;for(r>=1&&(o.appendChild(document.createTextNode("\n")),o=t()),a=document.createTextNode(i),s=n.parentNode;s!==e;){var l=s.cloneNode(!1);l.appendChild(a),a=l,s=s.parentNode}o.appendChild(a)});return r},Prism}),define("WoltLabSuite/Core/Upload",["AjaxRequest","Core","Dom/ChangeListener","Language","Dom/Util","Dom/Traverse"],function(e,t,i,n,o,r){"use strict";var a=function(){};return a.prototype={_createButton:function(){},_createFileElement:function(){},_createFileElements:function(){},_failure:function(){},_getParameters:function(){},_insertButton:function(){},_progress:function(){},_removeButton:function(){},_success:function(){},_upload:function(){},_uploadFiles:function(){}},a}),define("WoltLabSuite/Core/User",[],function(){"use strict";var e,t=!1;return{getLink:function(){return e},init:function(i,n,o){if(t)throw new Error("User has already been initialized.");Object.defineProperty(this,"userId",{value:i,writable:!1}),Object.defineProperty(this,"username",{value:n,writable:!1}),e=o,t=!0}}}),define("WoltLabSuite/Core/Ajax/Jsonp",["Core"],function(e){"use strict";return{send:function(t,i,n,o){if(t="string"==typeof t?t.trim():"",0===t.length)throw new Error("Expected a non-empty string for parameter 'url'.");if("function"!=typeof i)throw new TypeError("Expected a valid callback function for parameter 'success'.");o=e.extend({parameterName:"callback",timeout:10},o||{});var r,a="wcf_jsonp_"+e.getUuid().replace(/-/g,"").substr(0,8),s=window.setTimeout(function(){"function"==typeof n&&n(),window[a]=void 0,elRemove(r)},1e3*(~~o.timeout||10));window[a]=function(){window.clearTimeout(s),i.apply(null,arguments),window[a]=void 0,elRemove(r)},t+=-1===t.indexOf("?")?"?":"&",t+=o.parameterName+"="+a,r=elCreate("script"),r.async=!0,elAttr(r,"src",t),document.head.appendChild(r)}}}),define("WoltLabSuite/Core/Ui/Notification",["Language"],function(e){"use strict";var t=!1,i=null,n=null,o=null,r=null,a=null;return{show:function(s,l,c){t||(this._init(),i="function"==typeof l?l:null,n.className=c||"success",n.textContent=e.get(s||"wcf.global.success"),t=!0,o.classList.add("active"),r=setTimeout(a,2e3))},_init:function(){null===o&&(a=this._hide.bind(this),o=elCreate("div"),o.id="systemNotification",n=elCreate("p"),n.addEventListener(WCF_CLICK_EVENT,a),o.appendChild(n),document.body.appendChild(o))},_hide:function(){clearTimeout(r),o.classList.remove("active"),null!==i&&i(),t=!1}}}),define("prism/prism-meta",[],function(){return{markup:{title:"Markup",file:"markup"},html:{title:"HTML",file:"markup"},xml:{title:"XML",file:"markup"},svg:{title:"SVG",file:"markup"},mathml:{title:"MathML",file:"markup"},css:{title:"CSS",file:"css"},clike:{title:"C-like",file:"clike"},javascript:{title:"JavaScript",file:"javascript"},abap:{title:"ABAP",file:"abap"},actionscript:{title:"ActionScript",file:"actionscript"},ada:{title:"Ada",file:"ada"},apacheconf:{title:"Apache Configuration",file:"apacheconf"},apl:{title:"APL",file:"apl"},applescript:{title:"AppleScript",file:"applescript"},arduino:{title:"Arduino",file:"arduino"},arff:{title:"ARFF",file:"arff"},asciidoc:{title:"AsciiDoc",file:"asciidoc"},asm6502:{title:"6502 Assembly",file:"asm6502"},aspnet:{title:"ASP.NET (C#)",file:"aspnet"},autohotkey:{title:"AutoHotkey",file:"autohotkey"},autoit:{title:"AutoIt",file:"autoit"},bash:{title:"Bash",file:"bash"},basic:{title:"BASIC",file:"basic"},batch:{title:"Batch",file:"batch"},bison:{title:"Bison",file:"bison"},brainfuck:{title:"Brainfuck",file:"brainfuck"},bro:{title:"Bro",file:"bro"},c:{title:"C",file:"c"},csharp:{title:"C#",file:"csharp"},cpp:{title:"C++",file:"cpp"},coffeescript:{title:"CoffeeScript",file:"coffeescript"},clojure:{title:"Clojure",file:"clojure"},crystal:{title:"Crystal",file:"crystal"},csp:{title:"Content-Security-Policy",file:"csp"},"css-extras":{title:"CSS Extras",file:"css-extras"},d:{title:"D",file:"d"},dart:{title:"Dart",file:"dart"},diff:{title:"Diff",file:"diff"},django:{title:"Django/Jinja2",file:"django"},docker:{title:"Docker",file:"docker"},eiffel:{title:"Eiffel",file:"eiffel"},elixir:{title:"Elixir",file:"elixir"},elm:{title:"Elm",file:"elm"},erb:{title:"ERB",file:"erb"},erlang:{title:"Erlang",file:"erlang"},fsharp:{title:"F#",file:"fsharp"},flow:{title:"Flow",file:"flow"},fortran:{title:"Fortran",file:"fortran"},gedcom:{title:"GEDCOM",file:"gedcom"},gherkin:{title:"Gherkin",file:"gherkin"},git:{title:"Git",file:"git"},glsl:{title:"GLSL",file:"glsl"},gml:{title:"GameMaker Language",file:"gml"},go:{title:"Go",file:"go"},graphql:{title:"GraphQL",file:"graphql"},groovy:{title:"Groovy",file:"groovy"},haml:{title:"Haml",file:"haml"},handlebars:{title:"Handlebars",file:"handlebars"},haskell:{title:"Haskell",file:"haskell"},haxe:{title:"Haxe",file:"haxe"},http:{title:"HTTP",file:"http"},hpkp:{title:"HTTP Public-Key-Pins",file:"hpkp"},hsts:{title:"HTTP Strict-Transport-Security",file:"hsts"},ichigojam:{title:"IchigoJam",file:"ichigojam"},icon:{title:"Icon",file:"icon"},inform7:{title:"Inform 7",file:"inform7"},ini:{title:"Ini",file:"ini"},io:{title:"Io",file:"io"},j:{title:"J",file:"j"},java:{title:"Java",file:"java"},jolie:{title:"Jolie",file:"jolie"},json:{title:"JSON",file:"json"},julia:{title:"Julia",file:"julia"},keyman:{title:"Keyman",file:"keyman"},kotlin:{title:"Kotlin",file:"kotlin"},latex:{title:"LaTeX",file:"latex"},less:{title:"Less",file:"less"},liquid:{title:"Liquid",file:"liquid"},lisp:{title:"Lisp",file:"lisp"},livescript:{title:"LiveScript",file:"livescript"},lolcode:{title:"LOLCODE",file:"lolcode"},lua:{title:"Lua",file:"lua"},makefile:{title:"Makefile",file:"makefile"},markdown:{title:"Markdown",file:"markdown"},"markup-templating":{title:"Markup templating",file:"markup-templating"},matlab:{title:"MATLAB",file:"matlab"},mel:{title:"MEL",file:"mel"},mizar:{title:"Mizar",file:"mizar"},monkey:{title:"Monkey",file:"monkey"},n4js:{title:"N4JS",file:"n4js"},nasm:{title:"NASM",file:"nasm"},nginx:{title:"nginx",file:"nginx"},nim:{title:"Nim",file:"nim"},nix:{title:"Nix",file:"nix"},nsis:{title:"NSIS",file:"nsis"},objectivec:{title:"Objective-C",file:"objectivec"},ocaml:{title:"OCaml",file:"ocaml"},opencl:{title:"OpenCL",file:"opencl"},oz:{title:"Oz",file:"oz"},parigp:{title:"PARI/GP",file:"parigp"},parser:{title:"Parser",file:"parser"},pascal:{title:"Pascal",file:"pascal"},perl:{title:"Perl",file:"perl"},php:{title:"PHP",file:"php"},"php-extras":{title:"PHP Extras",file:"php-extras"},plsql:{title:"PL/SQL",file:"plsql"},powershell:{title:"PowerShell",file:"powershell"},processing:{title:"Processing",file:"processing"},prolog:{title:"Prolog",file:"prolog"},properties:{title:".properties",file:"properties"},protobuf:{title:"Protocol Buffers",file:"protobuf"},pug:{title:"Pug",file:"pug"},puppet:{title:"Puppet",file:"puppet"},pure:{title:"Pure",file:"pure"},python:{title:"Python",file:"python"},q:{title:"Q (kdb+ database)",file:"q"},qore:{title:"Qore",file:"qore"},r:{title:"R",file:"r"},jsx:{title:"React JSX",file:"jsx"},tsx:{title:"React TSX",file:"tsx"},renpy:{title:"Ren'py",file:"renpy"},reason:{title:"Reason",file:"reason"},rest:{title:"reST (reStructuredText)",file:"rest"},rip:{title:"Rip",file:"rip"},roboconf:{title:"Roboconf",file:"roboconf"},ruby:{title:"Ruby",file:"ruby"},rust:{title:"Rust",file:"rust"},sas:{title:"SAS",file:"sas"},sass:{title:"Sass (Sass)",file:"sass"},scss:{title:"Sass (Scss)",file:"scss"},scala:{title:"Scala",file:"scala"},scheme:{title:"Scheme",file:"scheme"},smalltalk:{title:"Smalltalk",file:"smalltalk"},smarty:{title:"Smarty",file:"smarty"},sql:{title:"SQL",file:"sql"},soy:{title:"Soy (Closure Template)",file:"soy"},stylus:{title:"Stylus",file:"stylus"},swift:{title:"Swift",file:"swift"},tap:{title:"TAP",file:"tap"},tcl:{title:"Tcl",file:"tcl"},textile:{title:"Textile",file:"textile"},tt2:{title:"Template Toolkit 2",file:"tt2"},twig:{title:"Twig",file:"twig"},typescript:{title:"TypeScript",file:"typescript"},vbnet:{title:"VB.Net",file:"vbnet"},velocity:{title:"Velocity",file:"velocity"},verilog:{title:"Verilog",file:"verilog"},vhdl:{title:"VHDL",file:"vhdl"},vim:{title:"vim",file:"vim"},"visual-basic":{title:"Visual Basic",file:"visual-basic"},wasm:{title:"WebAssembly",file:"wasm"},wiki:{title:"Wiki markup",file:"wiki"},xeora:{title:"Xeora",file:"xeora"},xojo:{title:"Xojo (REALbasic)",file:"xojo"},xquery:{title:"XQuery",file:"xquery"},yaml:{title:"YAML",file:"yaml"}}}),define("WoltLabSuite/Core/Bbcode/Code",["Language","WoltLabSuite/Core/Ui/Notification","WoltLabSuite/Core/Clipboard","WoltLabSuite/Core/Prism","prism/prism-meta"],function(e,t,i,n,o){"use strict";function r(e){var t;this.container=e,this.codeContainer=elBySel(".codeBoxCode > code",this.container),this.language=null;for(var i=0;i<this.codeContainer.classList.length;i++)(t=this.codeContainer.classList[i].match(/language-(.*)/))&&(this.language=t[1])}var a=function(e){return function(){var t=arguments;return new Promise(function(i,n){var o=function(){try{i(e.apply(null,t))}catch(e){n(e)}};window.requestIdleCallback?window.requestIdleCallback(o,{timeout:5e3}):setTimeout(o,0)})}};return r.processAll=function(){elBySelAll(".codeBox:not([data-processed])",document,function(e){elData(e,"processed","1");var t=new r(e);t.language&&t.highlight(),t.createCopyButton()})},r.prototype={createCopyButton:function(){var n=elBySel(".codeBoxHeader",this.container),o=elCreate("span");o.className="icon icon24 fa-files-o pointer jsTooltip",o.setAttribute("title",e.get("wcf.message.bbcode.code.copy")),o.addEventListener("click",function(){i.copyElementTextToClipboard(this.codeContainer).then(function(){t.show(e.get("wcf.message.bbcode.code.copy.success"))})}.bind(this)),n.appendChild(o)},highlight:function(){return this.language?o[this.language]?(this.container.classList.add("highlighting"),require(["prism/components/prism-"+o[this.language].file]).then(a(function(){var e=n.languages[this.language];if(!e)throw new Error("Invalid language "+language+" given.");var t=elCreate("div");return t.innerHTML=n.highlight(this.codeContainer.textContent,e,this.language),t}.bind(this))).then(a(function(e){var t=n.wscSplitIntoLines(e),i=elBySelAll("[data-number]",t),o=elBySelAll(".codeBoxLine > span",this.codeContainer);if(i.length!==o.length)throw new Error("Unreachable");for(var r=[],s=0,l=i.length;s<l;s+=50)r.push(a(function(e){for(var t=Math.min(e+50,l),n=e;n<t;n++)o[n].parentNode.replaceChild(i[n],o[n])})(s));return Promise.all(r)}.bind(this))).then(function(){this.container.classList.remove("highlighting"),this.container.classList.add("highlighted")}.bind(this))):Promise.reject(new Error("Unknown language "+this.language)):Promise.reject(new Error("No language detected"))}},r}),define("WoltLabSuite/Core/Bbcode/Collapsible",[],function(){"use strict";var e=elByClass("jsCollapsibleBbcode");return{observe:function(){for(var t,i;e.length;)t=e[0],i=null,elBySelAll(".toggleButton:not(.jsToggleButtonEnabled)",t,function(e){e.closest(".jsCollapsibleBbcode")===t&&(i=e)}),i&&function(e,t){var i=function(i){if(e.classList.toggle("collapsed")){if(t.textContent=elData(t,"title-expand"),i instanceof Event){var n=e.getBoundingClientRect().top;if(n<0){var o=window.pageYOffset+(n-100);o<0&&(o=0),window.scrollTo(window.pageXOffset,o)}}}else t.textContent=elData(t,"title-collapse")};t.classList.add("jsToggleButtonEnabled"),t.addEventListener(WCF_CLICK_EVENT,i),0!==e.scrollTop&&i(),e.addEventListener("scroll",function(){e.classList.contains("collapsed")&&i()})}(t,i),t.classList.remove("jsCollapsibleBbcode")}}}),define("WoltLabSuite/Core/Controller/Captcha",["Dictionary"],function(e){"use strict";var t=new e;return{add:function(e,i){if(t.has(e))throw new Error("Captcha with id '"+e+"' is already registered.");if("function"!=typeof i)throw new TypeError("Expected a valid callback for parameter 'callback'.");t.set(e,i)},delete:function(e){if(!t.has(e))throw new Error("Unknown captcha with id '"+e+"'.");t.delete(e)},has:function(e){return t.has(e)},getData:function(e){if(!t.has(e))throw new Error("Unknown captcha with id '"+e+"'.");return t.get(e)()}}}),define("WoltLabSuite/Core/Controller/Clipboard",["Ajax","Core","Dictionary","EventHandler","Language","List","ObjectMap","Dom/ChangeListener","Dom/Traverse","Dom/Util","Ui/Confirmation","Ui/SimpleDropdown","WoltLabSuite/Core/Ui/Page/Action","Ui/Screen"],function(e,t,i,n,o,r,a,s,l,c,u,d,h,f){"use strict";return{setup:function(){},reload:function(){},_initContainers:function(){},_loadMarkedItems:function(){},_markAll:function(){},_mark:function(){},_saveState:function(){},_executeAction:function(){},_executeProxyAction:function(){},_unmarkAll:function(){},_ajaxSetup:function(){},_ajaxSuccess:function(){},_rebuildMarkings:function(){},hideEditor:function(){},showEditor:function(){},unmark:function(){}}}),define("WoltLabSuite/Core/Image/ExifUtil",[],function(){"use strict";var e={SOI:216,APP0:224,APP1:225,APP2:226,APP3:227,APP4:228,APP5:229,APP6:230,APP7:231,APP8:232,APP9:233,APP10:234,APP11:235,APP12:236,APP13:237,APP14:238,COM:254};return{getExifBytesFromJpeg:function(t){return new Promise(function(i,n){if(!(t instanceof Blob||t instanceof File))return n(new TypeError("The argument must be a Blob or a File"));var o=new FileReader;o.addEventListener("error",function(){o.abort(),n(o.error)}),o.addEventListener("load",function(){var t=o.result,r=new Uint8Array(t),a=new Uint8Array;if(255!==r[0]&&r[1]!==e.SOI)return n(new Error("Not a JPEG"));for(var s=2;s<r.length&&255===r[s];){var l=2+(r[s+2]<<8|r[s+3]);if(r[s+1]===e.APP1){for(var c="",u=s+4;0!==r[u]&&u<r.length;u++)c+=String.fromCharCode(r[u]);if("Exif"===c||"http://ns.adobe.com/xap/1.0/"===c){var d=Array.prototype.slice.call(r,s,l+s),h=new Uint8Array(a.length+d.length);h.set(a),h.set(d,a.length),a=h}}s+=l}i(a)}),o.readAsArrayBuffer(t)})},removeExifData:function(t){return new Promise(function(i,n){if(!(t instanceof Blob||t instanceof File))return n(new TypeError("The argument must be a Blob or a File"));var o=new FileReader;o.addEventListener("error",function(){o.abort(),n(o.error)}),o.addEventListener("load",function(){var r=o.result,a=new Uint8Array(r);if(255!==a[0]&&a[1]!==e.SOI)return n(new Error("Not a JPEG"));for(var s=2;s<a.length&&255===a[s];){var l=2+(a[s+2]<<8|a[s+3]);if(a[s+1]===e.APP1){for(var c="",u=s+4;0!==a[u]&&u<a.length;u++)c+=String.fromCharCode(a[u]);if("Exif"===c||"http://ns.adobe.com/xap/1.0/"===c){var d=Array.prototype.slice.call(a,0,s),h=Array.prototype.slice.call(a,s+l);a=new Uint8Array(d.length+h.length),a.set(d,0),a.set(h,d.length)}}else s+=l}i(new Blob([a],{type:t.type}))}),o.readAsArrayBuffer(t)})},setExifData:function(t,i){return this.removeExifData(t).then(function(t){return new Promise(function(n){var o=new FileReader;o.addEventListener("error",function(){o.abort(),reject(o.error)}),o.addEventListener("load",function(){var r=o.result,a=new Uint8Array(r),s=2;255===a[2]&&a[3]===e.APP0&&(s+=2+(a[4]<<8|a[5]));var l=Array.prototype.slice.call(a,0,s),c=Array.prototype.slice.call(a,s);a=new Uint8Array(l.length+i.length+c.length),a.set(l),a.set(i,s),a.set(c,s+i.length),n(new Blob([a],{type:t.type}))}),o.readAsArrayBuffer(t)})})}}}),define("WoltLabSuite/Core/Image/ImageUtil",[],function(){"use strict";return{containsTransparentPixels:function(e){for(var t=e.getContext("2d").getImageData(0,0,e.width,e.height),i=3,n=t.data.length;i<n;i+=4)if(255!==t.data[i])return!0;return!1}}}),function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define("Pica",[],e);else{var t;t="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,t.pica=e()}}(function(){return function(){function e(t,i,n){function o(a,s){if(!i[a]){if(!t[a]){var l="function"==typeof require&&require;if(!s&&l)return l(a,!0);if(r)return r(a,!0);var c=new Error("Cannot find module '"+a+"'");throw c.code="MODULE_NOT_FOUND",c}var u=i[a]={exports:{}};t[a][0].call(u.exports,function(e){return o(t[a][1][e]||e)},u,u.exports,e,t,i,n)}return i[a].exports}for(var r="function"==typeof require&&require,a=0;a<n.length;a++)o(n[a]);return o}return e}()({1:[function(e,t,i){"use strict";function n(e){var t=e||[],i={js:t.indexOf("js")>=0,wasm:t.indexOf("wasm")>=0};r.call(this,i),this.features={js:i.js,wasm:i.wasm&&this.has_wasm},this.use(a),this.use(s)}var o=e("inherits"),r=e("multimath"),a=e("multimath/lib/unsharp_mask"),s=e("./mm_resize");o(n,r),n.prototype.resizeAndUnsharp=function(e,t){var i=this.resize(e,t);return e.unsharpAmount&&this.unsharp_mask(i,e.toWidth,e.toHeight,e.unsharpAmount,e.unsharpRadius,e.unsharpThreshold),i},t.exports=n},{"./mm_resize":4,inherits:15,multimath:16,"multimath/lib/unsharp_mask":19}],2:[function(e,t,i){"use strict";function n(e){return e<0?0:e>255?255:e}function o(e,t,i,o,r,a){var s,l,c,u,d,h,f,p,g,m,v,b=0,_=0;for(g=0;g<o;g++){for(d=0,m=0;m<r;m++){for(h=a[d++],f=a[d++],p=b+4*h|0,s=l=c=u=0;f>0;f--)v=a[d++],u=u+v*e[p+3]|0,c=c+v*e[p+2]|0,l=l+v*e[p+1]|0,s=s+v*e[p]|0,p=p+4|0;t[_+3]=n(u+8192>>14),t[_+2]=n(c+8192>>14),t[_+1]=n(l+8192>>14),t[_]=n(s+8192>>14),_=_+4*o|0}_=4*(g+1)|0,b=(g+1)*i*4|0}}function r(e,t,i,o,r,a){var s,l,c,u,d,h,f,p,g,m,v,b=0,_=0;for(g=0;g<o;g++){for(d=0,m=0;m<r;m++){for(h=a[d++],f=a[d++],p=b+4*h|0,s=l=c=u=0;f>0;f--)v=a[d++],u=u+v*e[p+3]|0,c=c+v*e[p+2]|0,l=l+v*e[p+1]|0,s=s+v*e[p]|0,p=p+4|0;t[_+3]=n(u+8192>>14),t[_+2]=n(c+8192>>14),t[_+1]=n(l+8192>>14),t[_]=n(s+8192>>14),_=_+4*o|0}_=4*(g+1)|0,b=(g+1)*i*4|0}}t.exports={convolveHorizontally:o,convolveVertically:r}},{}],3:[function(e,t,i){"use strict";t.exports="AGFzbQEAAAABFAJgBn9/f39/fwBgB39/f39/f38AAg8BA2VudgZtZW1vcnkCAAEDAwIAAQQEAXAAAAcZAghjb252b2x2ZQAACmNvbnZvbHZlSFYAAQkBAArmAwLBAwEQfwJAIANFDQAgBEUNACAFQQRqIRVBACEMQQAhDQNAIA0hDkEAIRFBACEHA0AgB0ECaiESAn8gBSAHQQF0IgdqIgZBAmouAQAiEwRAQQAhCEEAIBNrIRQgFSAHaiEPIAAgDCAGLgEAakECdGohEEEAIQlBACEKQQAhCwNAIBAoAgAiB0EYdiAPLgEAIgZsIAtqIQsgB0H/AXEgBmwgCGohCCAHQRB2Qf8BcSAGbCAKaiEKIAdBCHZB/wFxIAZsIAlqIQkgD0ECaiEPIBBBBGohECAUQQFqIhQNAAsgEiATagwBC0EAIQtBACEKQQAhCUEAIQggEgshByABIA5BAnRqIApBgMAAakEOdSIGQf8BIAZB/wFIG0EQdEGAgPwHcUEAIAZBAEobIAtBgMAAakEOdSIGQf8BIAZB/wFIG0EYdEEAIAZBAEobciAJQYDAAGpBDnUiBkH/ASAGQf8BSBtBCHRBgP4DcUEAIAZBAEobciAIQYDAAGpBDnUiBkH/ASAGQf8BSBtB/wFxQQAgBkEAShtyNgIAIA4gA2ohDiARQQFqIhEgBEcNAAsgDCACaiEMIA1BAWoiDSADRw0ACwsLIQACQEEAIAIgAyAEIAUgABAAIAJBACAEIAUgBiABEAALCw=="},{}],4:[function(e,t,i){"use strict";t.exports={name:"resize",fn:e("./resize"),wasm_fn:e("./resize_wasm"),wasm_src:e("./convolve_wasm_base64")}},{"./convolve_wasm_base64":3,"./resize":5,"./resize_wasm":8}],5:[function(e,t,i){"use strict";function n(e,t,i){for(var n=3,o=t*i*4|0;n<o;)e[n]=255,n=n+4|0}var o=e("./resize_filter_gen"),r=e("./convolve").convolveHorizontally,a=e("./convolve").convolveVertically;t.exports=function(e){var t=e.src,i=e.width,s=e.height,l=e.toWidth,c=e.toHeight,u=e.scaleX||e.toWidth/e.width,d=e.scaleY||e.toHeight/e.height,h=e.offsetX||0,f=e.offsetY||0,p=e.dest||new Uint8Array(l*c*4),g=void 0===e.quality?3:e.quality,m=e.alpha||!1,v=o(g,i,l,u,h),b=o(g,s,c,d,f),_=new Uint8Array(l*s*4);return r(t,_,i,s,l,v),a(_,p,s,l,c,b),m||n(p,l,c),p}},{"./convolve":2,"./resize_filter_gen":6}],6:[function(e,t,i){"use strict";function n(e){return Math.round(e*((1<<r)-1))}var o=e("./resize_filter_info"),r=14;t.exports=function(e,t,i,r,a){var s,l,c,u,d,h,f,p,g,m,v,b,_,y,w,C,E,L=o[e].filter,A=1/r,S=Math.min(1,r),I=o[e].win/S,x=Math.floor(2*(I+1)),D=new Int16Array((x+2)*i),k=0,T=!D.subarray||!D.set;for(s=0;s<i;s++){for(l=(s+.5)*A+a,c=Math.max(0,Math.floor(l-I)),u=Math.min(t-1,Math.ceil(l+I)),d=u-c+1,h=new Float32Array(d),f=new Int16Array(d),p=0,g=c,m=0;g<=u;g++,m++)v=L((g+.5-l)*S),p+=v,h[m]=v;for(b=0,m=0;m<h.length;m++)_=h[m]/p,b+=_,f[m]=n(_);for(f[i>>1]+=n(1-b),y=0;y<f.length&&0===f[y];)y++;if(y<f.length){for(w=f.length-1;w>0&&0===f[w];)w--;if(C=c+y,E=w-y+1,D[k++]=C,D[k++]=E,T)for(m=y;m<=w;m++)D[k++]=f[m];else D.set(f.subarray(y,w+1),k),k+=E}else D[k++]=0,D[k++]=0}return D}},{"./resize_filter_info":7}],7:[function(e,t,i){"use strict";t.exports=[{win:.5,filter:function(e){return e>=-.5&&e<.5?1:0}},{win:1,filter:function(e){if(e<=-1||e>=1)return 0;if(e>-1.1920929e-7&&e<1.1920929e-7)return 1;var t=e*Math.PI;return Math.sin(t)/t*(.54+.46*Math.cos(t/1))}},{win:2,filter:function(e){if(e<=-2||e>=2)return 0;if(e>-1.1920929e-7&&e<1.1920929e-7)return 1;var t=e*Math.PI;return Math.sin(t)/t*Math.sin(t/2)/(t/2)}},{win:3,filter:function(e){if(e<=-3||e>=3)return 0;if(e>-1.1920929e-7&&e<1.1920929e-7)return 1;var t=e*Math.PI;return Math.sin(t)/t*Math.sin(t/3)/(t/3)}}]},{}],8:[function(e,t,i){"use strict";function n(e,t,i){for(var n=3,o=t*i*4|0;n<o;)e[n]=255,n=n+4|0}function o(e){return new Uint8Array(e.buffer,0,e.byteLength)}function r(e,t,i){if(s)return void t.set(o(e),i);for(var n=i,r=0;r<e.length;r++){var a=e[r];t[n++]=255&a,t[n++]=a>>8&255}}var a=e("./resize_filter_gen"),s=!0;try{s=1===new Uint32Array(new Uint8Array([1,0,0,0]).buffer)[0]}catch(e){}t.exports=function(e){var t=e.src,i=e.width,o=e.height,s=e.toWidth,l=e.toHeight,c=e.scaleX||e.toWidth/e.width,u=e.scaleY||e.toHeight/e.height,d=e.offsetX||0,h=e.offsetY||0,f=e.dest||new Uint8Array(s*l*4),p=void 0===e.quality?3:e.quality,g=e.alpha||!1,m=a(p,i,s,c,d),v=a(p,o,l,u,h),b=this.__align(0+Math.max(t.byteLength,f.byteLength)),_=this.__align(b+o*s*4),y=this.__align(_+m.byteLength),w=y+v.byteLength,C=this.__instance("resize",w),E=new Uint8Array(this.__memory.buffer),L=new Uint32Array(this.__memory.buffer),A=new Uint32Array(t.buffer);return L.set(A),r(m,E,_),r(v,E,y),(C.exports.convolveHV||C.exports._convolveHV)(_,y,b,i,o,s,l),new Uint32Array(f.buffer).set(new Uint32Array(this.__memory.buffer,0,l*s)),g||n(f,s,l),f}},{"./resize_filter_gen":6}],9:[function(e,t,i){"use strict";function n(e,t){this.create=e,this.available=[],this.acquired={},this.lastId=1,this.timeoutId=0,this.idle=t||2e3}n.prototype.acquire=function(){var e,t=this;return 0!==this.available.length?e=this.available.pop():(e=this.create(),e.id=this.lastId++,e.release=function(){return t.release(e)}),this.acquired[e.id]=e,e},n.prototype.release=function(e){var t=this;delete this.acquired[e.id],e.lastUsed=Date.now(),this.available.push(e),0===this.timeoutId&&(this.timeoutId=setTimeout(function(){return t.gc()},100))},n.prototype.gc=function(){var e=this,t=Date.now();this.available=this.available.filter(function(i){return!(t-i.lastUsed>e.idle)||(i.destroy(),!1)}),0!==this.available.length?this.timeoutId=setTimeout(function(){return e.gc()},100):this.timeoutId=0},t.exports=n},{}],10:[function(e,t,i){"use strict";t.exports=function(e,t,i,n,o,r){var a=i/e,s=n/t,l=(2*r+2+1)/o;if(l>.5)return[[i,n]];var c=Math.ceil(Math.log(Math.min(a,s))/Math.log(l));if(c<=1)return[[i,n]];for(var u=[],d=0;d<c;d++){var h=Math.round(Math.pow(Math.pow(e,c-d-1)*Math.pow(i,d+1),1/c)),f=Math.round(Math.pow(Math.pow(t,c-d-1)*Math.pow(n,d+1),1/c));u.push([h,f])}return u}},{}],11:[function(e,t,i){"use strict";function n(e){var t=Math.round(e);return Math.abs(e-t)<r?t:Math.floor(e)}function o(e){var t=Math.round(e);return Math.abs(e-t)<r?t:Math.ceil(e)}var r=1e-5;t.exports=function(e){var t=e.toWidth/e.width,i=e.toHeight/e.height,r=n(e.srcTileSize*t)-2*e.destTileBorder,a=n(e.srcTileSize*i)-2*e.destTileBorder;if(r<1||a<1)throw new Error("Internal error in pica: target tile width/height is too small.");var s,l,c,u,d,h,f,p=[];for(u=0;u<e.toHeight;u+=a)for(c=0;c<e.toWidth;c+=r)s=c-e.destTileBorder,s<0&&(s=0),d=c+r+e.destTileBorder-s,s+d>=e.toWidth&&(d=e.toWidth-s),l=u-e.destTileBorder,l<0&&(l=0),h=u+a+e.destTileBorder-l,l+h>=e.toHeight&&(h=e.toHeight-l),f={toX:s,toY:l,toWidth:d,toHeight:h,toInnerX:c,toInnerY:u,toInnerWidth:r,toInnerHeight:a,offsetX:s/t-n(s/t),offsetY:l/i-n(l/i),scaleX:t,scaleY:i,x:n(s/t),y:n(l/i),width:o(d/t),height:o(h/i)},p.push(f);return p}},{}],12:[function(e,t,i){"use strict";function n(e){return Object.prototype.toString.call(e)}t.exports.isCanvas=function(e){var t=n(e);return"[object HTMLCanvasElement]"===t||"[object Canvas]"===t},t.exports.isImage=function(e){return"[object HTMLImageElement]"===n(e)},t.exports.limiter=function(e){function t(){i<e&&n.length&&(i++,n.shift()())}var i=0,n=[];return function(e){return new Promise(function(o,r){n.push(function(){e().then(function(e){o(e),i--,t()},function(e){r(e),i--,t()})}),t()})}},t.exports.cib_quality_name=function(e){switch(e){case 0:return"pixelated";case 1:return"low";case 2:return"medium"}return"high"},t.exports.cib_support=function(){return Promise.resolve().then(function(){if("undefined"==typeof createImageBitmap||"undefined"==typeof document)return!1;var e=document.createElement("canvas");return e.width=100,e.height=100,createImageBitmap(e,0,0,100,100,{resizeWidth:10,resizeHeight:10,resizeQuality:"high"}).then(function(t){var i=10===t.width;return t.close(),e=null,i})}).catch(function(){return!1})}},{}],13:[function(e,t,i){"use strict";t.exports=function(){var t,i=e("./mathlib");onmessage=function(e){var n=e.data.opts;t||(t=new i(e.data.features));var o=t.resizeAndUnsharp(n);postMessage({result:o},[o.buffer])}}},{"./mathlib":1}],14:[function(e,t,i){function n(e){e<.5&&(e=.5);var t=Math.exp(.527076)/e,i=Math.exp(-t),n=Math.exp(-2*t),o=(1-i)*(1-i)/(1+2*t*i-n);return a=o,s=o*(t-1)*i,l=o*(t+1)*i,c=-o*n,u=2*i,d=-n,h=(a+s)/(1-u-d),f=(l+c)/(1-u-d),new Float32Array([a,s,l,c,u,d,h,f])}function o(e,t,i,n,o,r){var a,s,l,c,u,d,h,f,p,g,m,v,b,_;for(p=0;p<r;p++){for(d=p*o,h=p,f=0,a=e[d],u=a*n[6],c=u,m=n[0],v=n[1],b=n[4],_=n[5],g=0;g<o;g++)s=e[d],l=s*m+a*v+c*b+u*_,u=c,c=l,a=s,i[f]=c,f++,d++;for(d--,f--,h+=r*(o-1),a=e[d],u=a*n[7],c=u,s=a,m=n[2],v=n[3],g=o-1;g>=0;g--)l=s*m+a*v+c*b+u*_,u=c,c=l,a=s,s=e[d],t[h]=i[f]+c,d--,f--,h-=r}}function r(e,t,i,r){if(r){var a=new Uint16Array(e.length),s=new Float32Array(Math.max(t,i)),l=n(r);o(e,a,s,l,t,i,r),o(a,e,s,l,i,t,r)}}var a,s,l,c,u,d,h,f;t.exports=r},{}],15:[function(e,t,i){"function"==typeof Object.create?t.exports=function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})}:t.exports=function(e,t){e.super_=t;var i=function(){};i.prototype=t.prototype,e.prototype=new i,e.prototype.constructor=e}},{}],16:[function(e,t,i){"use strict";function n(e){if(!(this instanceof n))return new n(e);var t=o({},s,e||{});if(this.options=t,this.__cache={},this.has_wasm=a(),this.__init_promise=null,this.__modules=t.modules||{},this.__memory=null,this.__wasm={},this.__isLE=1===new Uint32Array(new Uint8Array([1,0,0,0]).buffer)[0],!this.options.js&&!this.options.wasm)throw new Error('mathlib: at least "js" or "wasm" should be enabled')}var o=e("object-assign"),r=e("./lib/base64decode"),a=e("./lib/wa_detect"),s={js:!0,wasm:!0};n.prototype.use=function(e){return this.__modules[e.name]=e,this.has_wasm&&this.options.wasm&&e.wasm_fn?this[e.name]=e.wasm_fn:this[e.name]=e.fn,this},n.prototype.init=function(){if(this.__init_promise)return this.__init_promise;if(!this.options.js&&this.options.wasm&&!this.has_wasm)return Promise.reject(new Error('mathlib: only "wasm" was enabled, but it\'s not supported'));var e=this;return this.__init_promise=Promise.all(Object.keys(e.__modules).map(function(t){var i=e.__modules[t];return e.has_wasm&&e.options.wasm&&i.wasm_fn?e.__wasm[t]?null:WebAssembly.compile(e.__base64decode(i.wasm_src)).then(function(i){e.__wasm[t]=i}):null})).then(function(){return e}),this.__init_promise},n.prototype.__base64decode=r,n.prototype.__reallocate=function(e){if(!this.__memory)return this.__memory=new WebAssembly.Memory({initial:Math.ceil(e/65536)}),this.__memory;var t=this.__memory.buffer.byteLength;return t<e&&this.__memory.grow(Math.ceil((e-t)/65536)),this.__memory},n.prototype.__instance=function(e,t,i){if(t&&this.__reallocate(t),!this.__wasm[e]){var n=this.__modules[e];this.__wasm[e]=new WebAssembly.Module(this.__base64decode(n.wasm_src))}if(!this.__cache[e]){var r={memoryBase:0,memory:this.__memory,tableBase:0,table:new WebAssembly.Table({initial:0,element:"anyfunc"})};this.__cache[e]=new WebAssembly.Instance(this.__wasm[e],{env:o(r,i||{})})}return this.__cache[e]},n.prototype.__align=function(e,t){t=t||8;var i=e%t;return e+(i?t-i:0)},t.exports=n},{"./lib/base64decode":17,"./lib/wa_detect":23,"object-assign":24}],17:[function(e,t,i){"use strict";t.exports=function(e){for(var t=e.replace(/[\r\n=]/g,""),i=t.length,n=new Uint8Array(3*i>>2),o=0,r=0,a=0;a<i;a++)a%4==0&&a&&(n[r++]=o>>16&255,n[r++]=o>>8&255,n[r++]=255&o),o=o<<6|"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".indexOf(t.charAt(a));var s=i%4*6;return 0===s?(n[r++]=o>>16&255,n[r++]=o>>8&255,n[r++]=255&o):18===s?(n[r++]=o>>10&255,n[r++]=o>>2&255):12===s&&(n[r++]=o>>4&255),n}},{}],18:[function(e,t,i){"use strict";t.exports=function(e,t,i){for(var n,o,r,a,s,l=t*i,c=new Uint16Array(l),u=0;u<l;u++)n=e[4*u],o=e[4*u+1],r=e[4*u+2],s=n>=o&&n>=r?n:o>=r&&o>=n?o:r,a=n<=o&&n<=r?n:o<=r&&o<=n?o:r,c[u]=257*(s+a)>>1;return c}},{}],19:[function(e,t,i){"use strict";t.exports={name:"unsharp_mask",fn:e("./unsharp_mask"),wasm_fn:e("./unsharp_mask_wasm"),wasm_src:e("./unsharp_mask_wasm_base64")}},{"./unsharp_mask":20,"./unsharp_mask_wasm":21,"./unsharp_mask_wasm_base64":22}],20:[function(e,t,i){"use strict";var n=e("glur/mono16"),o=e("./hsl_l16");t.exports=function(e,t,i,r,a,s){var l,c,u,d,h,f,p,g,m,v,b,_,y;if(!(0===r||a<.5)){a>2&&(a=2);var w=o(e,t,i),C=new Uint16Array(w);n(C,t,i,a);for(var E=r/100*4096+.5|0,L=257*s|0,A=t*i,S=0;S<A;S++)_=2*(w[S]-C[S]),Math.abs(_)>=L&&(y=4*S,l=e[y],c=e[y+1],u=e[y+2],g=l>=c&&l>=u?l:c>=l&&c>=u?c:u,p=l<=c&&l<=u?l:c<=l&&c<=u?c:u,f=257*(g+p)>>1,p===g?d=h=0:(h=f<=32767?4095*(g-p)/(g+p)|0:4095*(g-p)/(510-g-p)|0,d=l===g?65535*(c-u)/(6*(g-p))|0:c===g?21845+(65535*(u-l)/(6*(g-p))|0):43690+(65535*(l-c)/(6*(g-p))|0)),f+=E*_+2048>>12,f>65535?f=65535:f<0&&(f=0),0===h?l=c=u=f>>8:(v=f<=32767?f*(4096+h)+2048>>12:f+((65535-f)*h+2048>>12),m=2*f-v>>8,v>>=8,b=d+21845&65535,l=b>=43690?m:b>=32767?m+(6*(v-m)*(43690-b)+32768>>16):b>=10922?v:m+(6*(v-m)*b+32768>>16),b=65535&d,c=b>=43690?m:b>=32767?m+(6*(v-m)*(43690-b)+32768>>16):b>=10922?v:m+(6*(v-m)*b+32768>>16),b=d-21845&65535,u=b>=43690?m:b>=32767?m+(6*(v-m)*(43690-b)+32768>>16):b>=10922?v:m+(6*(v-m)*b+32768>>16)),e[y]=l,e[y+1]=c,e[y+2]=u)}}},{"./hsl_l16":18,"glur/mono16":14}],21:[function(e,t,i){"use strict";t.exports=function(e,t,i,n,o,r){if(!(0===n||o<.5)){o>2&&(o=2);var a=t*i,s=4*a,l=2*a,c=2*a,u=4*Math.max(t,i),d=s,h=d+l,f=h+c,p=f+c,g=p+u,m=this.__instance("unsharp_mask",s+l+2*c+u+32,{exp:Math.exp}),v=new Uint32Array(e.buffer);new Uint32Array(this.__memory.buffer).set(v);var b=m.exports.hsl_l16||m.exports._hsl_l16;b(0,d,t,i),b=m.exports.blurMono16||m.exports._blurMono16,b(d,h,f,p,g,t,i,o),b=m.exports.unsharp||m.exports._unsharp,b(0,0,d,h,t,i,n,r),v.set(new Uint32Array(this.__memory.buffer,0,a))}}},{}],22:[function(e,t,i){"use strict"
+;t.exports="AGFzbQEAAAABMQZgAXwBfGACfX8AYAZ/f39/f38AYAh/f39/f39/fQBgBH9/f38AYAh/f39/f39/fwACGQIDZW52A2V4cAAAA2VudgZtZW1vcnkCAAEDBgUBAgMEBQQEAXAAAAdMBRZfX2J1aWxkX2dhdXNzaWFuX2NvZWZzAAEOX19nYXVzczE2X2xpbmUAAgpibHVyTW9ubzE2AAMHaHNsX2wxNgAEB3Vuc2hhcnAABQkBAAqJEAXZAQEGfAJAIAFE24a6Q4Ia+z8gALujIgOaEAAiBCAEoCIGtjgCECABIANEAAAAAAAAAMCiEAAiBbaMOAIUIAFEAAAAAAAA8D8gBKEiAiACoiAEIAMgA6CiRAAAAAAAAPA/oCAFoaMiArY4AgAgASAEIANEAAAAAAAA8L+gIAKioiIHtjgCBCABIAQgA0QAAAAAAADwP6AgAqKiIgO2OAIIIAEgBSACoiIEtow4AgwgASACIAegIAVEAAAAAAAA8D8gBqGgIgKjtjgCGCABIAMgBKEgAqO2OAIcCwu3AwMDfwR9CHwCQCADKgIUIQkgAyoCECEKIAMqAgwhCyADKgIIIQwCQCAEQX9qIgdBAEgiCA0AIAIgAC8BALgiDSADKgIYu6IiDiAJuyIQoiAOIAq7IhGiIA0gAyoCBLsiEqIgAyoCALsiEyANoqCgoCIPtjgCACACQQRqIQIgAEECaiEAIAdFDQAgBCEGA0AgAiAOIBCiIA8iDiARoiANIBKiIBMgAC8BALgiDaKgoKAiD7Y4AgAgAkEEaiECIABBAmohACAGQX9qIgZBAUoNAAsLAkAgCA0AIAEgByAFbEEBdGogAEF+ai8BACIIuCINIAu7IhGiIA0gDLsiEqKgIA0gAyoCHLuiIg4gCrsiE6KgIA4gCbsiFKKgIg8gAkF8aioCALugqzsBACAHRQ0AIAJBeGohAiAAQXxqIQBBACAFQQF0ayEHIAEgBSAEQQF0QXxqbGohBgNAIAghAyAALwEAIQggBiANIBGiIAO4Ig0gEqKgIA8iECAToqAgDiAUoqAiDyACKgIAu6CrOwEAIAYgB2ohBiAAQX5qIQAgAkF8aiECIBAhDiAEQX9qIgRBAUoNAAsLCwvfAgIDfwZ8AkAgB0MAAAAAWw0AIARE24a6Q4Ia+z8gB0MAAAA/l7ujIgyaEAAiDSANoCIPtjgCECAEIAxEAAAAAAAAAMCiEAAiDraMOAIUIAREAAAAAAAA8D8gDaEiCyALoiANIAwgDKCiRAAAAAAAAPA/oCAOoaMiC7Y4AgAgBCANIAxEAAAAAAAA8L+gIAuioiIQtjgCBCAEIA0gDEQAAAAAAADwP6AgC6KiIgy2OAIIIAQgDiALoiINtow4AgwgBCALIBCgIA5EAAAAAAAA8D8gD6GgIgujtjgCGCAEIAwgDaEgC6O2OAIcIAYEQCAFQQF0IQogBiEJIAIhCANAIAAgCCADIAQgBSAGEAIgACAKaiEAIAhBAmohCCAJQX9qIgkNAAsLIAVFDQAgBkEBdCEIIAUhAANAIAIgASADIAQgBiAFEAIgAiAIaiECIAFBAmohASAAQX9qIgANAAsLC7wBAQV/IAMgAmwiAwRAQQAgA2shBgNAIAAoAgAiBEEIdiIHQf8BcSECAn8gBEH/AXEiAyAEQRB2IgRB/wFxIgVPBEAgAyIIIAMgAk8NARoLIAQgBCAHIAIgA0kbIAIgBUkbQf8BcQshCAJAIAMgAk0EQCADIAVNDQELIAQgByAEIAMgAk8bIAIgBUsbQf8BcSEDCyAAQQRqIQAgASADIAhqQYECbEEBdjsBACABQQJqIQEgBkEBaiIGDQALCwvTBgEKfwJAIAazQwAAgEWUQwAAyEKVu0QAAAAAAADgP6CqIQ0gBSAEbCILBEAgB0GBAmwhDgNAQQAgAi8BACADLwEAayIGQQF0IgdrIAcgBkEASBsgDk8EQCAAQQJqLQAAIQUCfyAALQAAIgYgAEEBai0AACIESSIJRQRAIAYiCCAGIAVPDQEaCyAFIAUgBCAEIAVJGyAGIARLGwshCAJ/IAYgBE0EQCAGIgogBiAFTQ0BGgsgBSAFIAQgBCAFSxsgCRsLIgogCGoiD0GBAmwiEEEBdiERQQAhDAJ/QQAiCSAIIApGDQAaIAggCmsiCUH/H2wgD0H+AyAIayAKayAQQYCABEkbbSEMIAYgCEYEQCAEIAVrQf//A2wgCUEGbG0MAQsgBSAGayAGIARrIAQgCEYiBhtB//8DbCAJQQZsbUHVqgFBqtUCIAYbagshCSARIAcgDWxBgBBqQQx1aiIGQQAgBkEAShsiBkH//wMgBkH//wNIGyEGAkACfwJAIAxB//8DcSIFBEAgBkH//wFKDQEgBUGAIGogBmxBgBBqQQx2DAILIAZBCHYiBiEFIAYhBAwCCyAFIAZB//8Dc2xBgBBqQQx2IAZqCyIFQQh2IQcgBkEBdCAFa0EIdiIGIQQCQCAJQdWqAWpB//8DcSIFQanVAksNACAFQf//AU8EQEGq1QIgBWsgByAGa2xBBmxBgIACakEQdiAGaiEEDAELIAchBCAFQanVAEsNACAFIAcgBmtsQQZsQYCAAmpBEHYgBmohBAsCfyAGIgUgCUH//wNxIghBqdUCSw0AGkGq1QIgCGsgByAGa2xBBmxBgIACakEQdiAGaiAIQf//AU8NABogByIFIAhBqdUASw0AGiAIIAcgBmtsQQZsQYCAAmpBEHYgBmoLIQUgCUGr1QJqQf//A3EiCEGp1QJLDQAgCEH//wFPBEBBqtUCIAhrIAcgBmtsQQZsQYCAAmpBEHYgBmohBgwBCyAIQanVAEsEQCAHIQYMAQsgCCAHIAZrbEEGbEGAgAJqQRB2IAZqIQYLIAEgBDoAACABQQFqIAU6AAAgAUECaiAGOgAACyADQQJqIQMgAkECaiECIABBBGohACABQQRqIQEgC0F/aiILDQALCwsL"},{}],23:[function(e,t,i){"use strict";var n;t.exports=function(){if(void 0!==n)return n;if(n=!1,"undefined"==typeof WebAssembly)return n;try{var e=new Uint8Array([0,97,115,109,1,0,0,0,1,6,1,96,1,127,1,127,3,2,1,0,5,3,1,0,1,7,8,1,4,116,101,115,116,0,0,10,16,1,14,0,32,0,65,1,54,2,0,32,0,40,2,0,11]),t=new WebAssembly.Module(e);return 0!==new WebAssembly.Instance(t,{}).exports.test(4)&&(n=!0),n}catch(e){}return n}},{}],24:[function(e,t,i){"use strict";function n(e){if(null===e||void 0===e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}var o=Object.getOwnPropertySymbols,r=Object.prototype.hasOwnProperty,a=Object.prototype.propertyIsEnumerable;t.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},i=0;i<10;i++)t["_"+String.fromCharCode(i)]=i;if("0123456789"!==Object.getOwnPropertyNames(t).map(function(e){return t[e]}).join(""))return!1;var n={};return"abcdefghijklmnopqrst".split("").forEach(function(e){n[e]=e}),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},n)).join("")}catch(e){return!1}}()?Object.assign:function(e,t){for(var i,s,l=n(e),c=1;c<arguments.length;c++){i=Object(arguments[c]);for(var u in i)r.call(i,u)&&(l[u]=i[u]);if(o){s=o(i);for(var d=0;d<s.length;d++)a.call(i,s[d])&&(l[s[d]]=i[s[d]])}}return l}},{}],25:[function(e,t,i){var n=arguments[3],o=arguments[4],r=arguments[5],a=JSON.stringify;t.exports=function(e,t){function i(e){m[e]=!0;for(var t in o[e][1]){var n=o[e][1][t];m[n]||i(n)}}for(var s,l=Object.keys(r),c=0,u=l.length;c<u;c++){var d=l[c],h=r[d].exports;if(h===e||h&&h.default===e){s=d;break}}if(!s){s=Math.floor(Math.pow(16,8)*Math.random()).toString(16);for(var f={},c=0,u=l.length;c<u;c++){var d=l[c];f[d]=d}o[s]=["function(require,module,exports){"+e+"(self); }",f]}var p=Math.floor(Math.pow(16,8)*Math.random()).toString(16),g={};g[s]=s,o[p]=["function(require,module,exports){var f = require("+a(s)+");(f.default ? f.default : f)(self);}",g];var m={};i(p);var v="("+n+")({"+Object.keys(m).map(function(e){return a(e)+":["+o[e][0]+","+a(o[e][1])+"]"}).join(",")+"},{},["+a(p)+"])",b=window.URL||window.webkitURL||window.mozURL||window.msURL,_=new Blob([v],{type:"text/javascript"});if(t&&t.bare)return _;var y=b.createObjectURL(_),w=new Worker(y);return w.objectURL=y,w}},{}],"/":[function(e,t,i){"use strict";function n(e,t){return a(e)||r(e,t)||o()}function o(){throw new TypeError("Invalid attempt to destructure non-iterable instance")}function r(e,t){var i=[],n=!0,o=!1,r=void 0;try{for(var a,s=e[Symbol.iterator]();!(n=(a=s.next()).done)&&(i.push(a.value),!t||i.length!==t);n=!0);}catch(e){o=!0,r=e}finally{try{n||null==s.return||s.return()}finally{if(o)throw r}}return i}function a(e){if(Array.isArray(e))return e}function s(){return{value:u(p),destroy:function(){if(this.value.terminate(),"undefined"!=typeof window){var e=window.URL||window.webkitURL||window.mozURL||window.msURL;e&&e.revokeObjectURL&&this.value.objectURL&&e.revokeObjectURL(this.value.objectURL)}}}}function l(e){if(!(this instanceof l))return new l(e);this.options=c({},C,e||{});var t="lk_".concat(this.options.concurrency);this.__limit=v[t]||f.limiter(this.options.concurrency),v[t]||(v[t]=this.__limit),this.features={js:!1,wasm:!1,cib:!1,ww:!1},this.__workersPool=null,this.__requested_features=[],this.__mathlib=null}var c=e("object-assign"),u=e("webworkify"),d=e("./lib/mathlib"),h=e("./lib/pool"),f=e("./lib/utils"),p=e("./lib/worker"),g=e("./lib/stepper"),m=e("./lib/tiler"),v={},b=!1;try{"undefined"!=typeof navigator&&navigator.userAgent&&(b=navigator.userAgent.indexOf("Safari")>=0)}catch(e){}var _=1;"undefined"!=typeof navigator&&(_=Math.min(navigator.hardwareConcurrency||1,4));var y,w,C={tile:1024,concurrency:_,features:["js","wasm","ww"],idle:2e3},E={quality:3,alpha:!1,unsharpAmount:0,unsharpRadius:0,unsharpThreshold:0};l.prototype.init=function(){var t=this;if(this.__initPromise)return this.__initPromise;if(!1!==y&&!0!==y&&(y=!1,"undefined"!=typeof ImageData&&"undefined"!=typeof Uint8ClampedArray))try{new ImageData(new Uint8ClampedArray(400),10,10),y=!0}catch(e){}!1!==w&&!0!==w&&(w=!1,"undefined"!=typeof ImageBitmap&&(ImageBitmap.prototype&&ImageBitmap.prototype.close?w=!0:this.debug("ImageBitmap does not support .close(), disabled")));var i=this.options.features.slice();if(i.indexOf("all")>=0&&(i=["cib","wasm","js","ww"]),this.__requested_features=i,this.__mathlib=new d(i),i.indexOf("ww")>=0&&"undefined"!=typeof window&&"Worker"in window)try{e("webworkify")(function(){}).terminate(),this.features.ww=!0;var n="wp_".concat(JSON.stringify(this.options));v[n]?this.__workersPool=v[n]:(this.__workersPool=new h(s,this.options.idle),v[n]=this.__workersPool)}catch(e){}var o,r=this.__mathlib.init().then(function(e){c(t.features,e.features)});return o=w?f.cib_support().then(function(e){if(t.features.cib&&i.indexOf("cib")<0)return void t.debug("createImageBitmap() resize supported, but disabled by config");i.indexOf("cib")>=0&&(t.features.cib=e)}):Promise.resolve(!1),this.__initPromise=Promise.all([r,o]).then(function(){return t}),this.__initPromise},l.prototype.resize=function(e,t,i){var o=this;this.debug("Start resize...");var r=c({},E);if(isNaN(i)?i&&(r=c(r,i)):r=c(r,{quality:i}),r.toWidth=t.width,r.toHeight=t.height,r.width=e.naturalWidth||e.width,r.height=e.naturalHeight||e.height,0===t.width||0===t.height)return Promise.reject(new Error("Invalid output size: ".concat(t.width,"x").concat(t.height)));r.unsharpRadius>2&&(r.unsharpRadius=2);var a=!1,s=null;r.cancelToken&&(s=r.cancelToken.then(function(e){throw a=!0,e},function(e){throw a=!0,e}));var l=Math.ceil(Math.max(3,2.5*r.unsharpRadius|0));return this.init().then(function(){if(a)return s;if(o.features.cib){var i=t.getContext("2d",{alpha:Boolean(r.alpha)});return o.debug("Resize via createImageBitmap()"),createImageBitmap(e,{resizeWidth:r.toWidth,resizeHeight:r.toHeight,resizeQuality:f.cib_quality_name(r.quality)}).then(function(e){if(a)return s;if(!r.unsharpAmount)return i.drawImage(e,0,0),e.close(),i=null,o.debug("Finished!"),t;o.debug("Unsharp result");var n=document.createElement("canvas");n.width=r.toWidth,n.height=r.toHeight;var l=n.getContext("2d",{alpha:Boolean(r.alpha)});l.drawImage(e,0,0),e.close();var c=l.getImageData(0,0,r.toWidth,r.toHeight);return o.__mathlib.unsharp(c.data,r.toWidth,r.toHeight,r.unsharpAmount,r.unsharpRadius,r.unsharpThreshold),i.putImageData(c,0,0),c=l=n=i=null,o.debug("Finished!"),t})}var u={},d=function(e){return Promise.resolve().then(function(){return o.features.ww?new Promise(function(t,i){var n=o.__workersPool.acquire();s&&s.catch(function(e){return i(e)}),n.value.onmessage=function(e){n.release(),e.data.err?i(e.data.err):t(e.data.result)},n.value.postMessage({opts:e,features:o.__requested_features,preload:{wasm_nodule:o.__mathlib.__}},[e.src.buffer])}):o.__mathlib.resizeAndUnsharp(e,u)})},h=function(e,t,i){var n,r,c,u=function(t){return o.__limit(function(){if(a)return s;var l;if(f.isCanvas(e))o.debug("Get tile pixel data"),l=n.getImageData(t.x,t.y,t.width,t.height);else{o.debug("Draw tile imageBitmap/image to temporary canvas");var u=document.createElement("canvas");u.width=t.width,u.height=t.height;var h=u.getContext("2d",{alpha:Boolean(i.alpha)});h.globalCompositeOperation="copy",h.drawImage(r||e,t.x,t.y,t.width,t.height,0,0,t.width,t.height),o.debug("Get tile pixel data"),l=h.getImageData(0,0,t.width,t.height),h=u=null}var p={src:l.data,width:t.width,height:t.height,toWidth:t.toWidth,toHeight:t.toHeight,scaleX:t.scaleX,scaleY:t.scaleY,offsetX:t.offsetX,offsetY:t.offsetY,quality:i.quality,alpha:i.alpha,unsharpAmount:i.unsharpAmount,unsharpRadius:i.unsharpRadius,unsharpThreshold:i.unsharpThreshold};return o.debug("Invoke resize math"),Promise.resolve().then(function(){return d(p)}).then(function(e){if(a)return s;l=null;var i;if(o.debug("Convert raw rgba tile result to ImageData"),y)i=new ImageData(new Uint8ClampedArray(e),t.toWidth,t.toHeight);else if(i=c.createImageData(t.toWidth,t.toHeight),i.data.set)i.data.set(e);else for(var n=i.data.length-1;n>=0;n--)i.data[n]=e[n];return o.debug("Draw tile"),b?c.putImageData(i,t.toX,t.toY,t.toInnerX-t.toX,t.toInnerY-t.toY,t.toInnerWidth+1e-5,t.toInnerHeight+1e-5):c.putImageData(i,t.toX,t.toY,t.toInnerX-t.toX,t.toInnerY-t.toY,t.toInnerWidth,t.toInnerHeight),null})})};return Promise.resolve().then(function(){if(c=t.getContext("2d",{alpha:Boolean(i.alpha)}),f.isCanvas(e))return n=e.getContext("2d",{alpha:Boolean(i.alpha)}),null;if(f.isImage(e))return w?(o.debug("Decode image via createImageBitmap"),createImageBitmap(e).then(function(e){r=e})):null;throw new Error('".from" should be image or canvas')}).then(function(){function e(){r&&(r.close(),r=null)}if(a)return s;o.debug("Calculate tiles");var n=m({width:i.width,height:i.height,srcTileSize:o.options.tile,toWidth:i.toWidth,toHeight:i.toHeight,destTileBorder:l}),c=n.map(function(e){return u(e)});return o.debug("Process tiles"),Promise.all(c).then(function(){return o.debug("Finished!"),e(),t},function(t){throw e(),t})})},p=g(r.width,r.height,r.toWidth,r.toHeight,o.options.tile,l);return function e(t,i,o,r){if(a)return s;var l=t.shift(),u=n(l,2),d=u[0],f=u[1],p=0===t.length;r=c({},r,{toWidth:d,toHeight:f,quality:p?r.quality:Math.min(1,r.quality)});var g;return p||(g=document.createElement("canvas"),g.width=d,g.height=f),h(i,p?o:g,r).then(function(){return p?o:(r.width=d,r.height=f,e(t,g,o,r))})}(p,e,t,r)})},l.prototype.resizeBuffer=function(e){var t=this,i=c({},E,e);return this.init().then(function(){return t.__mathlib.resizeAndUnsharp(i)})},l.prototype.toBlob=function(e,t,i){return t=t||"image/png",new Promise(function(n){if(e.toBlob)return void e.toBlob(function(e){return n(e)},t,i);for(var o=atob(e.toDataURL(t,i).split(",")[1]),r=o.length,a=new Uint8Array(r),s=0;s<r;s++)a[s]=o.charCodeAt(s);n(new Blob([a],{type:t}))})},l.prototype.debug=function(){},t.exports=l},{"./lib/mathlib":1,"./lib/pool":9,"./lib/stepper":10,"./lib/tiler":11,"./lib/utils":12,"./lib/worker":13,"object-assign":24,webworkify:25}]},{},[])("/")}),define("WoltLabSuite/Core/Image/Resizer",["WoltLabSuite/Core/FileUtil","WoltLabSuite/Core/Image/ExifUtil","Pica"],function(e,t,i){"use strict";function n(){}var o=new i({features:["js","wasm","ww"]});return n.prototype={maxWidth:800,maxHeight:600,quality:.8,fileType:"image/jpeg",setMaxWidth:function(e){return null==e&&(e=n.prototype.maxWidth),this.maxWidth=e,this},setMaxHeight:function(e){return null==e&&(e=n.prototype.maxHeight),this.maxHeight=e,this},setQuality:function(e){return null==e&&(e=n.prototype.quality),this.quality=e,this},setFileType:function(e){return null==e&&(e=n.prototype.fileType),this.fileType=e,this},saveFile:function(i,n,r,a){r=r||this.fileType,a=a||this.quality;var s=n.match(/(.+)(\..+?)$/);return o.toBlob(i.image,r,a).then(function(e){return"image/jpeg"===r&&void 0!==i.exif?t.setExifData(e,i.exif):e}).then(function(t){return e.blobToFile(t,s[1]+"_autoscaled")})},loadFile:function(e){var i=void 0;"image/jpeg"===e.type&&(i=t.getExifBytesFromJpeg(e));var n=new Promise(function(t,i){var n=new FileReader,o=new Image;n.addEventListener("load",function(){o.src=n.result}),n.addEventListener("error",function(){n.abort(),i(n.error)}),o.addEventListener("error",i),o.addEventListener("load",function(){t(o)}),n.readAsDataURL(e)});return Promise.all([i,n]).then(function(e){return{exif:e[0],image:e[1]}})},resize:function(e,t,i,n,r,a){t=t||this.maxWidth,i=i||this.maxHeight,n=n||this.quality,r=r||!1;var s=document.createElement("canvas"),l=Math.min(t,e.width),c=Math.min(i,e.height);if(e.width<=l&&e.height<=c&&!r)return Promise.resolve(void 0);var u=Math.min(l/e.width,c/e.height);s.width=Math.floor(e.width*u),s.height=Math.floor(e.height*u);var d=1;n>=.8?d=3:n>=.4&&(d=2);var h={quality:d,cancelToken:a,alpha:!0};return o.resize(e,s,h)}},n}),define("WoltLabSuite/Core/Language/Chooser",["Dictionary","Language","Dom/Traverse","Dom/Util","ObjectMap","Ui/SimpleDropdown"],function(e,t,i,n,o,r){"use strict";var a=new e,s=!1,l=new o,c=null;return{init:function(e,t,i,n,o,r){if(!a.has(t)){var s=elById(e);if(null===s)throw new Error("Expected a valid container id, cannot find '"+t+"'.");var l=elById(t);null===l&&(l=elCreate("input"),elAttr(l,"type","hidden"),elAttr(l,"id",t),elAttr(l,"name",t),elAttr(l,"value",i),s.appendChild(l)),this._initElement(t,l,i,n,o,r)}},_setup:function(){s||(s=!0,c=this._submit.bind(this))},_initElement:function(e,o,s,u,d,h){var f;"DD"===o.parentNode.nodeName?(f=elCreate("div"),f.className="dropdown",n.prepend(f,o.parentNode)):(f=o.parentNode,f.classList.add("dropdown")),elHide(o);var p=elCreate("a");p.className="dropdownToggle dropdownIndicator boxFlag box24 inputPrefix"+("DD"===o.parentNode.nodeName?" button":""),f.appendChild(p);var g=elCreate("ul");g.className="dropdownMenu",f.appendChild(g);var m,v,b,_,y=function(t){var n=~~elData(t.currentTarget,"language-id"),o=i.childByClass(g,"active");null!==o&&o.classList.remove("active"),n&&t.currentTarget.classList.add("active"),this._select(e,n,t.currentTarget)}.bind(this);for(var w in u)if(u.hasOwnProperty(w)){var C=u[w];b=elCreate("li"),b.className="boxFlag",b.addEventListener(WCF_CLICK_EVENT,y),elData(b,"language-id",w),void 0!==C.languageCode&&elData(b,"language-code",C.languageCode),g.appendChild(b),m=elCreate("a"),m.className="box24",b.appendChild(m),v=elCreate("img"),elAttr(v,"src",C.iconPath),elAttr(v,"alt",""),v.className="iconFlag",m.appendChild(v),_=elCreate("span"),_.textContent=C.languageName,m.appendChild(_),w==s&&(p.innerHTML=b.firstChild.innerHTML)}if(h)b=elCreate("li"),b.className="dropdownDivider",g.appendChild(b),b=elCreate("li"),elData(b,"language-id",0),b.addEventListener(WCF_CLICK_EVENT,y),g.appendChild(b),m=elCreate("a"),m.textContent=t.get("wcf.global.language.noSelection"),b.appendChild(m),0===s&&(p.innerHTML=b.firstChild.innerHTML),b.addEventListener(WCF_CLICK_EVENT,y);else if(0===s){p.innerHTML=null;var E=elCreate("div");p.appendChild(E),_=elCreate("span"),_.className="icon icon24 fa-question",E.appendChild(_),_=elCreate("span"),_.textContent=t.get("wcf.global.language.noSelection"),E.appendChild(_)}r.init(p),a.set(e,{callback:d,dropdownMenu:g,dropdownToggle:p,element:o});var L=i.parentByTag(o,"FORM");if(null!==L){L.addEventListener("submit",c);var A=l.get(L);void 0===A&&(A=[],l.set(L,A)),A.push(e)}},_select:function(e,t,i){var n=a.get(e);if(void 0===i){for(var o=n.dropdownMenu.childNodes,r=0,s=o.length;r<s;r++){var l=o[r];if(~~elData(l,"language-id")===t){i=l;break}}if(void 0===i)throw new Error("Cannot select unknown language id '"+t+"'")}n.element.value=t,n.dropdownToggle.innerHTML=i.firstChild.innerHTML,a.set(e,n),"function"==typeof n.callback&&n.callback(i)},_submit:function(e){for(var t,i=l.get(e.currentTarget),n=0,o=i.length;n<o;n++)t=elCreate("input"),t.type="hidden",t.name=i[n],t.value=this.getLanguageId(i[n]),e.currentTarget.appendChild(t)},getChooser:function(e){var t=a.get(e);if(void 0===t)throw new Error("Expected a valid language chooser input element, '"+e+"' is not i18n input field.");return t},getLanguageId:function(e){return~~this.getChooser(e).element.value},removeChooser:function(e){a.has(e)&&a.delete(e)},setLanguageId:function(e,t){if(void 0===a.get(e))throw new Error("Expected a valid  input element, '"+e+"' is not i18n input field.");this._select(e,t)}}}),define("WoltLabSuite/Core/Language/Input",["Core","Dictionary","Language","ObjectMap","StringUtil","Dom/Traverse","Dom/Util","Ui/SimpleDropdown"],function(e,t,i,n,o,r,a,s){"use strict";var l=new t,c=!1,u=new n,d=new t,h=null,f=null;return{init:function(e,i,n,r){if(!d.has(e)){var a=elById(e);if(null===a)throw new Error("Expected a valid element id, cannot find '"+e+"'.");this._setup();var s=new t;for(var l in i)i.hasOwnProperty(l)&&s.set(~~l,o.unescapeHTML(i[l]));d.set(e,s),this._initElement(e,a,s,n,r)}},registerCallback:function(e,t,i){if(!d.has(e))throw new Error("Unknown element id '"+e+"'.");l.get(e).callbacks.set(t,i)},unregister:function(e){if(!d.has(e))throw new Error("Unknown element id '"+e+"'.");d.delete(e),l.delete(e)},_setup:function(){c||(c=!0,h=this._dropdownToggle.bind(this),f=this._submit.bind(this))},_initElement:function(e,n,o,c,d){var p=n.parentNode;if(!p.classList.contains("inputAddon")){p=elCreate("div"),p.className="inputAddon"+("TEXTAREA"===n.nodeName?" inputAddonTextarea":""),elData(p,"input-id",e);var g=document.activeElement===n;n.parentNode.insertBefore(p,n),p.appendChild(n),g&&n.focus()}p.classList.add("dropdown");var m=elCreate("span");m.className="button dropdownToggle inputPrefix";var v=elCreate("span");v.textContent=i.get("wcf.global.button.disabledI18n"),m.appendChild(v),p.insertBefore(m,n);var b=elCreate("ul");b.className="dropdownMenu",a.insertAfter(b,m);var _,y=function(t,i){var n=~~elData(t.currentTarget,"language-id"),o=r.childByClass(b,"active");null!==o&&o.classList.remove("active"),n&&t.currentTarget.classList.add("active"),this._select(e,n,i||!1)}.bind(this);for(var w in c)c.hasOwnProperty(w)&&(_=elCreate("li"),elData(_,"language-id",w),v=elCreate("span"),v.textContent=c[w],_.appendChild(v),_.addEventListener(WCF_CLICK_EVENT,y),b.appendChild(_));!0!==d&&(_=elCreate("li"),_.className="dropdownDivider",b.appendChild(_),_=elCreate("li"),elData(_,"language-id",0),v=elCreate("span"),v.textContent=i.get("wcf.global.button.disabledI18n"),_.appendChild(v),_.addEventListener(WCF_CLICK_EVENT,y),b.appendChild(_));var C=null;if(!0===d||o.size)for(var E=0,L=b.childElementCount;E<L;E++)if(~~elData(b.children[E],"language-id")===LANGUAGE_ID){C=b.children[E];break}s.init(m),s.registerCallback(p.id,h),l.set(e,{buttonLabel:m.children[0],callbacks:new t,element:n,languageId:0,isEnabled:!0,forceSelection:d});var A=r.parentByTag(n,"FORM");if(null!==A){A.addEventListener("submit",f);var S=u.get(A);void 0===S&&(S=[],u.set(A,S)),S.push(e)}null!==C&&y({currentTarget:C},!0)},_select:function(e,i,n){for(var o,r=l.get(e),a=s.getDropdownMenu(r.element.closest(".inputAddon").id),c="",u=0,h=a.childElementCount;u<h;u++){o=a.children[u];var f=elData(o,"language-id");f.length&&i===~~f&&(c=o.children[0].textContent)}if(r.languageId!==i){var p=d.get(e);r.languageId&&p.set(r.languageId,r.element.value),0===i?d.set(e,new t):(r.buttonLabel.classList.contains("active")||!0===n)&&(r.element.value=p.has(i)?p.get(i):""),r.buttonLabel.textContent=c,r.buttonLabel.classList[i?"add":"remove"]("active"),r.languageId=i}n||(r.element.blur(),r.element.focus()),r.callbacks.has("select")&&r.callbacks.get("select")(r.element)},_dropdownToggle:function(e,t){if("open"===t)for(var i,n,o=s.getDropdownMenu(e),r=elData(elById(e),"input-id"),a=l.get(r),c=d.get(r),u=0,h=o.childElementCount;u<h;u++)if(i=o.children[u],n=~~elData(i,"language-id")){var f=!1;a.languageId&&(f=n===a.languageId?""===a.element.value.trim():!c.get(n)),i.classList[f?"add":"remove"]("missingValue")}},_submit:function(e){for(var t,i,n,o,r=u.get(e.currentTarget),a=0,s=r.length;a<s;a++)i=r[a],t=l.get(i),t.isEnabled&&(o=d.get(i),t.callbacks.has("submit")&&t.callbacks.get("submit")(t.element),t.languageId&&o.set(t.languageId,t.element.value),o.size&&(o.forEach(function(t,o){n=elCreate("input"),n.type="hidden",n.name=i+"_i18n["+o+"]",n.value=t,e.currentTarget.appendChild(n)}),t.element.removeAttribute("name")))},getValues:function(e){var t=l.get(e);if(void 0===t)throw new Error("Expected a valid i18n input element, '"+e+"' is not i18n input field.");var i=d.get(e);return i.set(t.languageId,t.element.value),i},setValues:function(i,n){var o=l.get(i);if(void 0===o)throw new Error("Expected a valid i18n input element, '"+i+"' is not i18n input field.");if(e.isPlainObject(n)&&(n=t.fromObject(n)),o.element.value="",n.has(0))return o.element.value=n.get(0),n.delete(0),d.set(i,n),void this._select(i,0,!0);d.set(i,n),o.languageId=0,this._select(i,LANGUAGE_ID,!0)},disable:function(e){var t=l.get(e);if(void 0===t)throw new Error("Expected a valid element, '"+e+"' is not an i18n input field.");if(t.isEnabled){t.isEnabled=!1,elHide(t.buttonLabel.parentNode);var i=t.buttonLabel.parentNode.parentNode;i.classList.remove("inputAddon"),i.classList.remove("dropdown")}},enable:function(e){var t=l.get(e);if(void 0===t)throw new Error("Expected a valid i18n input element, '"+e+"' is not i18n input field.");if(!t.isEnabled){t.isEnabled=!0,elShow(t.buttonLabel.parentNode);var i=t.buttonLabel.parentNode.parentNode;i.classList.add("inputAddon"),i.classList.add("dropdown")}},isEnabled:function(e){var t=l.get(e);if(void 0===t)throw new Error("Expected a valid i18n input element, '"+e+"' is not i18n input field.");return t.isEnabled},validate:function(e,t){var i=l.get(e);if(void 0===i)throw new Error("Expected a valid i18n input element, '"+e+"' is not i18n input field.");if(!i.isEnabled)return!0;var n=d.get(e),o=s.getDropdownMenu(i.element.parentNode.id);i.languageId&&n.set(i.languageId,i.element.value);for(var r,a,c=!1,u=!1,h=0,f=o.childElementCount;h<f;h++)if(r=o.children[h],a=~~elData(r,"language-id"))if(n.has(a)&&0!==n.get(a).length){if(c)return!1;u=!0}else{if(u)return!1;c=!0}return!c||t}}}),define("WoltLabSuite/Core/Language/Text",["Core","./Input"],function(e,t){"use strict";return{init:function(e,i,n,o){var r=elById(e);if(!r||"TEXTAREA"!==r.nodeName||!r.classList.contains("wysiwygTextarea"))throw new Error('Expected <textarea class="wysiwygTextarea" /> for id \''+e+"'.");t.init(e,i,n,o),t.registerCallback(e,"select",this._callbackSelect.bind(this)),t.registerCallback(e,"submit",this._callbackSubmit.bind(this))},_callbackSelect:function(e){void 0!==window.jQuery&&window.jQuery(e).redactor("code.set",e.value)},_callbackSubmit:function(e){void 0!==window.jQuery&&(e.value=window.jQuery(e).redactor("code.get"))}}}),define("WoltLabSuite/Core/Media/Editor",["Ajax","Core","Dictionary","Dom/ChangeListener","Dom/Traverse","Language","Ui/Dialog","Ui/Notification","WoltLabSuite/Core/Language/Chooser","WoltLabSuite/Core/Language/Input","EventKey"],function(e,t,i,n,o,r,a,s,l,c,u){"use strict";var d=function(){};return d.prototype={_ajaxSetup:function(){},_ajaxSuccess:function(){},_close:function(){},_keyPress:function(){},_saveData:function(){},_updateLanguageFields:function(){},edit:function(){}},d}),define("WoltLabSuite/Core/Media/Upload",["Core","DateUtil","Dom/ChangeListener","Dom/Traverse","Dom/Util","EventHandler","Language","Permission","Upload","User","WoltLabSuite/Core/FileUtil"],function(e,t,i,n,o,r,a,s,l,c,u){"use strict";var d=function(){};return d.prototype={_createFileElement:function(){},_getParameters:function(){},_success:function(){},_uploadFiles:function(){},_createButton:function(){},_createFileElements:function(){},_failure:function(){},_insertButton:function(){},_progress:function(){},_removeButton:function(){},_upload:function(){}},d}),define("WoltLabSuite/Core/Media/List/Upload",["Core","Dom/Util","../Upload"],function(e,t,i){"use strict";var n=function(){};return n.prototype={_createButton:function(){},_success:function(){},_upload:function(){},_createFileElement:function(){},_getParameters:function(){},_uploadFiles:function(){},_createFileElements:function(){},_failure:function(){},_insertButton:function(){},_progress:function(){},_removeButton:function(){}},n}),define("WoltLabSuite/Core/Media/Clipboard",["Ajax","Dom/ChangeListener","EventHandler","Language","Ui/Dialog","Ui/Notification","WoltLabSuite/Core/Controller/Clipboard","WoltLabSuite/Core/Media/Editor","WoltLabSuite/Core/Media/List/Upload"],function(e,t,i,n,o,r,a,s,l){"use strict";var c=function(){};return c.prototype={init:function(){},_ajaxSetup:function(){},_ajaxSuccess:function(){},_clipboardAction:function(){},_dialogSetup:function(){},_edit:function(){},_setCategory:function(){}},c}),define("WoltLabSuite/Core/Notification/Handler",["Ajax","Core","EventHandler","StringUtil"],function(e,t,i,n){"use strict";if(!("Promise"in window&&"Notification"in window))return{setup:function(){}};var o=!1,r="",a=0,s=window.TIME_NOW,l=null,c=0;return{setup:function(e){if(e=t.extend({enableNotifications:!1,icon:"",sessionKeepAlive:0},e),r=e.icon,c=60*e.sessionKeepAlive,this._prepareNextRequest(),document.addEventListener("visibilitychange",this._onVisibilityChange.bind(this)),window.addEventListener("storage",this._onStorage.bind(this)),this._onVisibilityChange(null),e.enableNotifications)switch(window.Notification.permission){case"granted":o=!0;break;case"default":window.Notification.requestPermission(function(e){"granted"===e&&(o=!0)})}},_onVisibilityChange:function(e){if(null!==e&&!document.hidden){(Date.now()-a)/6e4>4&&(this._resetTimer(),this._dispatchRequest())}a=document.hidden?Date.now():0},_getNextDelay:function(){if(0===a)return 5;var e=~~((Date.now()-a)/6e4);return e<15?5:e<30?10:15},_resetTimer:function(){null!==l&&(window.clearTimeout(l),l=null)},_prepareNextRequest:function(){this._resetTimer();var e=Math.min(this._getNextDelay(),c);l=window.setTimeout(this._dispatchRequest.bind(this),6e4*e)},_dispatchRequest:function(){var t={};i.fire("com.woltlab.wcf.notification","beforePoll",t),t.lastRequestTimestamp=s,e.api(this,{parameters:t})},_onStorage:function(){this._prepareNextRequest();var e,n,o=!1;try{e=window.localStorage.getItem(t.getStoragePrefix()+"notification"),n=window.localStorage.getItem(t.getStoragePrefix()+"keepAliveData"),e=JSON.parse(e),n=JSON.parse(n)}catch(e){o=!0}o||i.fire("com.woltlab.wcf.notification","onStorage",{pollData:e,keepAliveData:n})},_ajaxSuccess:function(e){var n=!1,o=e.returnValues.keepAliveData,r=e.returnValues.pollData;window.WCF.System.PushNotification.executeCallbacks(o);try{window.localStorage.setItem(t.getStoragePrefix()+"notification",JSON.stringify(r)),window.localStorage.setItem(t.getStoragePrefix()+"keepAliveData",JSON.stringify(o))}catch(e){n=!0,window.console.log(e)}n||this._prepareNextRequest(),s=e.returnValues.lastRequestTimestamp,i.fire("com.woltlab.wcf.notification","afterPoll",r),this._showNotification(r)},_showNotification:function(e){if(o&&"object"==typeof e.notification&&"string"==typeof e.notification.message){var t=new window.Notification(e.notification.title,{body:n.unescapeHTML(e.notification.message),icon:r});t.onclick=function(){window.focus(),t.close(),window.location=e.notification.link}}},_ajaxSetup:function(){return{data:{actionName:"poll",className:"wcf\\data\\session\\SessionAction"},ignoreError:!window.ENABLE_DEBUG_MODE,silent:!window.ENABLE_DEBUG_MODE}}}}),define("WoltLabSuite/Core/Ui/Redactor/DragAndDrop",["Dictionary","EventHandler","Language"],function(e,t,i){"use strict";var n=function(){};return n.prototype={init:function(){},_dragOver:function(){},_drop:function(){},_dragLeave:function(){},_setup:function(){}},n}),define("WoltLabSuite/Core/Ui/DragAndDrop",["Core","EventHandler","WoltLabSuite/Core/Ui/Redactor/DragAndDrop"],function(e,t,i){return{register:function(n){var o=e.getUuid();n=e.extend({element:"",elementId:"",onDrop:function(e){},onGlobalDrop:function(e){}}),t.add("com.woltlab.wcf.redactor2","dragAndDrop_"+n.elementId,n.onDrop),t.add("com.woltlab.wcf.redactor2","dragAndDrop_globalDrop_"+n.elementId,n.onGlobalDrop),i.init({uuid:o,$editor:[n.element],$element:[{id:n.elementId}]})}}}),define("WoltLabSuite/Core/Ui/Suggestion",["Ajax","Core","Ui/SimpleDropdown"],function(e,t,i){"use strict";function n(e,t){this.init(e,t)}return n.prototype={init:function(e,i){if(this._dropdownMenu=null,this._value="",this._element=elById(e),null===this._element)throw new Error("Expected a valid element id.");if(this._options=t.extend({ajax:{actionName:"getSearchResultList",className:"",interfaceName:"wcf\\data\\ISearchAction",parameters:{data:{}}},callbackSelect:null,excludedSearchValues:[],threshold:3},i),"function"!=typeof this._options.callbackSelect)throw new Error("Expected a valid callback for option 'callbackSelect'.");this._element.addEventListener(WCF_CLICK_EVENT,function(e){e.stopPropagation()}),this._element.addEventListener("keydown",this._keyDown.bind(this)),this._element.addEventListener("keyup",this._keyUp.bind(this))},addExcludedValue:function(e){-1===this._options.excludedSearchValues.indexOf(e)&&this._options.excludedSearchValues.push(e)},removeExcludedValue:function(e){var t=this._options.excludedSearchValues.indexOf(e);-1!==t&&this._options.excludedSearchValues.splice(t,1)},isActive:function(){return null!==this._dropdownMenu&&i.isOpen(this._element.id)},_keyDown:function(e){if(!this.isActive())return!0;if(13!==e.keyCode&&27!==e.keyCode&&38!==e.keyCode&&40!==e.keyCode)return!0;for(var t,n=0,o=this._dropdownMenu.childElementCount;n<o&&(t=this._dropdownMenu.children[n],!t.classList.contains("active"));)n++;if(13===e.keyCode)i.close(this._element.id),this._select(t);else if(27===e.keyCode){if(!i.isOpen(this._element.id))return!0;i.close(this._element.id)}else{var r=0;38===e.keyCode?r=(0===n?o:n)-1:40===e.keyCode&&(r=n+1)===o&&(r=0),r!==n&&(t.classList.remove("active"),this._dropdownMenu.children[r].classList.add("active"))}return e.preventDefault(),!1},_select:function(e){var t=e instanceof Event
+;t&&(e=e.currentTarget.parentNode);var i=e.children[0];this._options.callbackSelect(this._element.id,{objectId:elData(i,"object-id"),value:e.textContent,type:elData(i,"type")}),t&&this._element.focus()},_keyUp:function(t){var n=t.currentTarget.value.trim();if(this._value!==n){if(n.length<this._options.threshold)return null!==this._dropdownMenu&&i.close(this._element.id),void(this._value=n);this._value=n,e.api(this,{parameters:{data:{excludedSearchValues:this._options.excludedSearchValues,searchString:n}}})}},_ajaxSetup:function(){return{data:this._options.ajax}},_ajaxSuccess:function(e){if(null===this._dropdownMenu?(this._dropdownMenu=elCreate("div"),this._dropdownMenu.className="dropdownMenu",i.initFragment(this._element,this._dropdownMenu)):this._dropdownMenu.innerHTML="",e.returnValues.length){for(var t,n,o,r=0,a=e.returnValues.length;r<a;r++)n=e.returnValues[r],t=elCreate("a"),n.icon?(t.className="box16",t.innerHTML=n.icon+" <span></span>",t.children[1].textContent=n.label):t.textContent=n.label,elData(t,"object-id",n.objectID),n.type&&elData(t,"type",n.type),t.addEventListener(WCF_CLICK_EVENT,this._select.bind(this)),o=elCreate("li"),0===r&&(o.className="active"),o.appendChild(t),this._dropdownMenu.appendChild(o);i.open(this._element.id,!0)}else i.close(this._element.id)}},n}),define("WoltLabSuite/Core/Ui/ItemList",["Core","Dictionary","Language","Dom/Traverse","EventKey","WoltLabSuite/Core/Ui/Suggestion","Ui/SimpleDropdown"],function(e,t,i,n,o,r,a){"use strict";var s="",l=new t,c=!1,u=null,d=null,h=null,f=null,p=null,g=null;return{init:function(t,i,o){var s=elById(t);if(null===s)throw new Error("Expected a valid element id, '"+t+"' is invalid.");if(l.has(t)){var c=l.get(t);for(var u in c)if(c.hasOwnProperty(u)){var d=c[u];d instanceof Element&&d.parentNode&&elRemove(d)}a.destroy(t),l.delete(t)}o=e.extend({ajax:{actionName:"getSearchResultList",className:"",data:{}},excludedSearchValues:[],maxItems:-1,maxLength:-1,restricted:!1,isCSV:!1,callbackChange:null,callbackSubmit:null,callbackSyncShadow:null,callbackSetupValues:null,submitFieldName:""},o);var h=n.parentByTag(s,"FORM");if(null!==h&&!1===o.isCSV){if(!o.submitFieldName.length&&"function"!=typeof o.callbackSubmit)throw new Error("Expected a valid function for option 'callbackSubmit', a non-empty value for option 'submitFieldName' or enabling the option 'submitFieldCSV'.");h.addEventListener("submit",function(){var e=this.getValues(t);if(o.submitFieldName.length)for(var i,n=0,r=e.length;n<r;n++)i=elCreate("input"),i.type="hidden",i.name=o.submitFieldName.replace(/{$objectId}/,e[n].objectId),i.value=e[n].value,h.appendChild(i);else o.callbackSubmit(h,e)}.bind(this))}this._setup();var f=this._createUI(s,o),p=new r(t,{ajax:o.ajax,callbackSelect:this._addItem.bind(this),excludedSearchValues:o.excludedSearchValues});if(l.set(t,{dropdownMenu:null,element:f.element,list:f.list,listItem:f.element.parentNode,options:o,shadow:f.shadow,suggestion:p}),i=o.callbackSetupValues?o.callbackSetupValues():f.values.length?f.values:i,Array.isArray(i))for(var g,m=0,v=i.length;m<v;m++)g=i[m],"string"==typeof g&&(g={objectId:0,value:g}),this._addItem(t,g)},getValues:function(e){if(!l.has(e))throw new Error("Element id '"+e+"' is unknown.");var t=l.get(e),i=[];return elBySelAll(".item > span",t.list,function(e){i.push({objectId:~~elData(e,"object-id"),value:e.textContent,type:elData(e,"type")})}),i},setValues:function(e,t){if(!l.has(e))throw new Error("Element id '"+e+"' is unknown.");var i,o,r=l.get(e),a=n.childrenByClass(r.list,"item");for(i=0,o=a.length;i<o;i++)this._removeItem(null,a[i],!0);for(i=0,o=t.length;i<o;i++)this._addItem(e,t[i])},_setup:function(){c||(c=!0,u=this._keyDown.bind(this),d=this._keyPress.bind(this),h=this._keyUp.bind(this),f=this._paste.bind(this),p=this._removeItem.bind(this),g=this._blur.bind(this))},_createUI:function(e,t){var i=elCreate("ol");i.className="inputItemList"+(e.disabled?" disabled":""),elData(i,"element-id",e.id),i.addEventListener(WCF_CLICK_EVENT,function(t){t.target===i&&e.focus()});var n=elCreate("li");n.className="input",i.appendChild(n),e.addEventListener("keydown",u),e.addEventListener("keypress",d),e.addEventListener("keyup",h),e.addEventListener("paste",f);var o=e===document.activeElement;o&&e.blur(),e.addEventListener("blur",g),e.parentNode.insertBefore(i,e),n.appendChild(e),o&&window.setTimeout(function(){e.focus()},1),-1!==t.maxLength&&elAttr(e,"maxLength",t.maxLength);var r=null,a=[];if(t.isCSV){r=elCreate("input"),r.className="itemListInputShadow",r.type="hidden",r.name=e.name,e.removeAttribute("name"),i.parentNode.insertBefore(r,i);for(var s,l=e.value.split(","),c=0,p=l.length;c<p;c++)s=l[c].trim(),s.length&&a.push(s);if("TEXTAREA"===e.nodeName){var m=elCreate("input");m.type="text",e.parentNode.insertBefore(m,e),m.id=e.id,elRemove(e),e=m}}return{element:e,list:i,shadow:r,values:a}},_acceptsNewItems:function(e){var t=l.get(e);return-1===t.options.maxItems||t.list.childElementCount-1<t.options.maxItems},_handleLimit:function(e){var t=l.get(e);this._acceptsNewItems(e)?t.element.disabled&&(t.element.disabled=!1,t.element.removeAttribute("placeholder")):t.element.disabled||(t.element.disabled=!0,elAttr(t.element,"placeholder",i.get("wcf.global.form.input.maxItems")))},_keyDown:function(e){var t=e.currentTarget,i=t.parentNode.previousElementSibling;s=t.id,8===e.keyCode?0===t.value.length&&null!==i&&(i.classList.contains("active")?this._removeItem(null,i):i.classList.add("active")):27===e.keyCode&&null!==i&&i.classList.contains("active")&&i.classList.remove("active")},_keyPress:function(e){if(o.Enter(e)||o.Comma(e)){if(e.preventDefault(),l.get(e.currentTarget.id).options.restricted)return;var t=e.currentTarget.value.trim();t.length&&this._addItem(e.currentTarget.id,{objectId:0,value:t})}},_paste:function(e){var t="";t="object"==typeof window.clipboardData?window.clipboardData.getData("Text"):e.clipboardData.getData("text/plain");var i=e.currentTarget,n=i.id,o=~~elAttr(i,"maxLength");t.split(/,/).forEach(function(e){e=e.trim(),o&&e.length>o&&(e=e.substr(0,o)),e.length>0&&this._acceptsNewItems(n)&&this._addItem(n,{objectId:0,value:e})}.bind(this)),e.preventDefault()},_keyUp:function(e){var t=e.currentTarget;if(t.value.length>0){var i=t.parentNode.previousElementSibling;null!==i&&i.classList.remove("active")}},_addItem:function(e,t){var i=l.get(e),n=elCreate("li");n.className="item";var o=elCreate("span");if(o.className="content",elData(o,"object-id",t.objectId),t.type&&elData(o,"type",t.type),o.textContent=t.value,n.appendChild(o),!i.element.disabled){var r=elCreate("a");r.className="icon icon16 fa-times",r.addEventListener(WCF_CLICK_EVENT,p),n.appendChild(r)}i.list.insertBefore(n,i.listItem),i.suggestion.addExcludedValue(t.value),i.element.value="",i.element.disabled||this._handleLimit(e);var a=this._syncShadow(i);"function"==typeof i.options.callbackChange&&(null===a&&(a=this.getValues(e)),i.options.callbackChange(e,a))},_removeItem:function(e,t,i){t=null===e?t:e.currentTarget.parentNode;var n=t.parentNode,o=elData(n,"element-id"),r=l.get(o);r.suggestion.removeExcludedValue(t.children[0].textContent),n.removeChild(t),i||r.element.focus(),this._handleLimit(o);var a=this._syncShadow(r);"function"==typeof r.options.callbackChange&&(null===a&&(a=this.getValues(o)),r.options.callbackChange(o,a))},_syncShadow:function(e){if(!e.options.isCSV)return null;if("function"==typeof e.options.callbackSyncShadow)return e.options.callbackSyncShadow(e);for(var t="",i=this.getValues(e.element.id),n=0,o=i.length;n<o;n++)t+=(t.length?",":"")+i[n].value;return e.shadow.value=t,i},_blur:function(e){var t=e.currentTarget,i=l.get(t.id);if(!i.options.restricted){var n=t.value.trim();n.length&&(i.suggestion&&i.suggestion.isActive()||this._addItem(t.id,{objectId:0,value:n}))}}}}),define("WoltLabSuite/Core/Ui/Page/JumpTo",["Language","ObjectMap","Ui/Dialog"],function(e,t,i){"use strict";var n=null,o=null,r=null,a=new t,s=null;return{init:function(e,t){if(null===(t=t||null)){var i=elData(e,"link");t=i?function(e){window.location=i.replace(/pageNo=%d/,"pageNo="+e)}:function(){}}else if("function"!=typeof t)throw new TypeError("Expected a valid function for parameter 'callback'.");a.has(e)||elBySelAll(".jumpTo",e,function(i){i.addEventListener(WCF_CLICK_EVENT,this._click.bind(this,e)),a.set(e,{callback:t})}.bind(this))},_click:function(t,o){n=t,"object"==typeof o&&o.preventDefault(),i.open(this);var a=elData(t,"pages");s.value=a,s.setAttribute("max",a),s.select(),r.textContent=e.get("wcf.page.jumpTo.description").replace(/#pages#/,a)},_keyUp:function(e){if(13===e.which&&!1===o.disabled)return void this._submit();var t=~~s.value;t<1||t>~~elAttr(s,"max")?o.disabled=!0:o.disabled=!1},_submit:function(e){a.get(n).callback(~~s.value),i.close(this)},_dialogSetup:function(){var t='<dl><dt><label for="jsPaginationPageNo">'+e.get("wcf.page.jumpTo")+'</label></dt><dd><input type="number" id="jsPaginationPageNo" value="1" min="1" max="1" class="tiny"><small></small></dd></dl><div class="formSubmit"><button class="buttonPrimary">'+e.get("wcf.global.button.submit")+"</button></div>";return{id:"paginationOverlay",options:{onSetup:function(e){s=elByTag("input",e)[0],s.addEventListener("keyup",this._keyUp.bind(this)),r=elByTag("small",e)[0],o=elByTag("button",e)[0],o.addEventListener(WCF_CLICK_EVENT,this._submit.bind(this))}.bind(this),title:e.get("wcf.global.page.pagination")},source:t}}}}),define("WoltLabSuite/Core/Ui/Pagination",["Core","Language","ObjectMap","StringUtil","WoltLabSuite/Core/Ui/Page/JumpTo"],function(e,t,i,n,o){"use strict";function r(e,t){this.init(e,t)}return r.prototype={SHOW_LINKS:11,init:function(t,i){this._element=t,this._options=e.extend({activePage:1,maxPage:1,callbackShouldSwitch:null,callbackSwitch:null},i),"function"!=typeof this._options.callbackShouldSwitch&&(this._options.callbackShouldSwitch=null),"function"!=typeof this._options.callbackSwitch&&(this._options.callbackSwitch=null),this._element.classList.add("pagination"),this._rebuild(this._element)},_rebuild:function(){var e=!1;this._element.innerHTML="";var i,n=elCreate("ul"),r=elCreate("li");r.className="skip",n.appendChild(r);var a="icon icon24 fa-chevron-left";this._options.activePage>1?(i=elCreate("a"),i.className=a+" jsTooltip",i.href="#",i.title=t.get("wcf.global.page.previous"),i.rel="prev",r.appendChild(i),i.addEventListener(WCF_CLICK_EVENT,this.switchPage.bind(this,this._options.activePage-1))):(r.innerHTML='<span class="'+a+'"></span>',r.classList.add("disabled")),n.appendChild(this._createLink(1));var s=this.SHOW_LINKS-4,l=this._options.activePage-2;l<0&&(l=0);var c=this._options.maxPage-(this._options.activePage+1);c<0&&(c=0),this._options.activePage>1&&this._options.activePage<this._options.maxPage&&s--;var u=s/2,d=this._options.activePage,h=this._options.activePage;d<1&&(d=1),h<1&&(h=1),h>this._options.maxPage-1&&(h=this._options.maxPage-1),l>=u?d-=u:(d-=l,h+=u-l),c>=u?h+=u:(h+=c,d-=u-c),h=Math.ceil(h),d=Math.ceil(d),d<1&&(d=1),h>this._options.maxPage&&(h=this._options.maxPage);var f='<a class="jsTooltip" title="'+t.get("wcf.page.jumpTo")+'">&hellip;</a>';d>1&&(d-1<2?n.appendChild(this._createLink(2)):(r=elCreate("li"),r.className="jumpTo",r.innerHTML=f,n.appendChild(r),e=!0));for(var p=d+1;p<h;p++)n.appendChild(this._createLink(p));h<this._options.maxPage&&(this._options.maxPage-h<2?n.appendChild(this._createLink(this._options.maxPage-1)):(r=elCreate("li"),r.className="jumpTo",r.innerHTML=f,n.appendChild(r),e=!0)),n.appendChild(this._createLink(this._options.maxPage)),r=elCreate("li"),r.className="skip",n.appendChild(r),a="icon icon24 fa-chevron-right",this._options.activePage<this._options.maxPage?(i=elCreate("a"),i.className=a+" jsTooltip",i.href="#",i.title=t.get("wcf.global.page.next"),i.rel="next",r.appendChild(i),i.addEventListener(WCF_CLICK_EVENT,this.switchPage.bind(this,this._options.activePage+1))):(r.innerHTML='<span class="'+a+'"></span>',r.classList.add("disabled")),e&&(elData(n,"pages",this._options.maxPage),o.init(n,this.switchPage.bind(this))),this._element.appendChild(n)},_createLink:function(e){var i=elCreate("li");if(e!==this._options.activePage){var o=elCreate("a");o.textContent=n.addThousandsSeparator(e),o.addEventListener(WCF_CLICK_EVENT,this.switchPage.bind(this,e)),i.appendChild(o)}else i.classList.add("active"),i.innerHTML="<span>"+n.addThousandsSeparator(e)+'</span><span class="invisible">'+t.get("wcf.page.pagePosition",{pageNo:e,pages:this._options.maxPage})+"</span>";return i},getActivePage:function(){return this._options.activePage},getElement:function(){return this._element},getMaxPage:function(){return this._options.maxPage},switchPage:function(t,i){if("object"==typeof i&&(i.preventDefault(),i.currentTarget&&elData(i.currentTarget,"tooltip"))){var n=elById("balloonTooltip");n&&(e.triggerEvent(i.currentTarget,"mouseleave"),n.style.removeProperty("top"),n.style.removeProperty("bottom"))}if((t=~~t)>0&&this._options.activePage!==t&&t<=this._options.maxPage){if(null!==this._options.callbackShouldSwitch&&!0!==this._options.callbackShouldSwitch(t))return;this._options.activePage=t,this._rebuild(),null!==this._options.callbackSwitch&&this._options.callbackSwitch(t)}}},r}),define("WoltLabSuite/Core/Ui/Scroll",["Dom/Util"],function(e){"use strict";var t=null,i=null,n=null,o=null;return{element:function(o,r){if(!(o instanceof Element))throw new TypeError("Expected a valid DOM element.");if(void 0!==r&&"function"!=typeof r)throw new TypeError("Expected a valid callback function.");if(!document.body.contains(o))throw new Error("Element must be part of the visible DOM.");if(null!==t)throw new Error("Cannot scroll to element, a concurrent request is running.");r&&(t=r,null===i&&(i=this._onScroll.bind(this)),window.addEventListener("scroll",i));var a=e.offset(o).top;if(null===n){n=50;var s=elById("pageHeaderPanel");if(null!==s){var l=window.getComputedStyle(s).position;n="fixed"===l||"static"===l?s.offsetHeight:0}}n>0&&(a<=n?a=0:a-=n);var c=window.pageYOffset;window.scrollTo({left:0,top:a,behavior:"smooth"}),window.setTimeout(function(){c===window.pageYOffset&&this._onScroll()}.bind(this),100)},_onScroll:function(){null!==o&&window.clearTimeout(o),o=window.setTimeout(function(){null!==t&&t(),window.removeEventListener("scroll",i),t=null,o=null},100)}}}),define("WoltLabSuite/Core/Controller/Media/List",["Dom/ChangeListener","EventHandler","WoltLabSuite/Core/Controller/Clipboard","WoltLabSuite/Core/Media/Clipboard","WoltLabSuite/Core/Media/Editor","WoltLabSuite/Core/Media/List/Upload"],function(e,t,i,n,o,r){"use strict";var a=function(){};return a.prototype={init:function(){},_addButtonEventListeners:function(){},_deleteCallback:function(){},_deleteMedia:function(e){},_edit:function(){}},a}),define("WoltLabSuite/Core/Controller/Notice/Dismiss",["Ajax"],function(e){"use strict";return{setup:function(){var e=elByClass("jsDismissNoticeButton");if(e.length)for(var t=this._click.bind(this),i=0,n=e.length;i<n;i++)e[i].addEventListener(WCF_CLICK_EVENT,t)},_click:function(t){var i=t.currentTarget;e.apiOnce({data:{actionName:"dismiss",className:"wcf\\data\\notice\\NoticeAction",objectIDs:[elData(i,"object-id")]},success:function(){elRemove(i.parentNode)}})}}}),define("WoltLabSuite/Core/Form/Builder/Field/Dependency/Manager",["Dictionary","Dom/ChangeListener","EventHandler","List","Dom/Traverse","Dom/Util","ObjectMap"],function(e,t,i,n,o,r,a){"use strict";var s=!1,l=!0,c=new n,u=new e,d=new n,h=new e,f=new a;return{_hide:function(t){elHide(t),c.add(t),t.classList.contains("tabMenuContent")&&elBySelAll("li",o.prevByClass(t,"tabMenu"),function(e){elData(e,"name")===elData(t,"name")&&elHide(e)}),elBySelAll("[max], [maxlength], [min], [required]",t,function(t){var i=new e,n=elAttr(t,"max");n&&(i.set("max",n),t.removeAttribute("max"));var o=elAttr(t,"maxlength");o&&(i.set("maxlength",o),t.removeAttribute("maxlength"));var r=elAttr(t,"min");r&&(i.set("min",r),t.removeAttribute("min")),t.required&&(i.set("required",!0),t.removeAttribute("required")),f.set(t,i)})},_show:function(e){elShow(e),c.delete(e),e.classList.contains("tabMenuContent")&&elBySelAll("li",o.prevByClass(e,"tabMenu"),function(t){elData(t,"name")===elData(e,"name")&&elShow(t)}),elBySelAll("input, select",e,function(t){for(var i=t.parentNode;i!==e&&"none"!==i.style.getPropertyValue("display");)i=i.parentNode;if(i===e&&f.has(t)){var n=f.get(t);n.has("max")&&elAttr(t,"max",n.get("max")),n.has("maxlength")&&elAttr(t,"maxlength",n.get("maxlength")),n.has("min")&&elAttr(t,"min",n.get("min")),n.has("required")&&elAttr(t,"required",""),f.delete(t)}})},addDependency:function(e){var t=e.getDependentNode();h.has(t.id)?h.get(t.id).push(e):h.set(t.id,[e]);for(var i=e.getFields(),n=0,o=i.length;n<o;n++){var a=i[n],s=r.identify(a);u.has(s)||(u.set(s,a),"INPUT"!==a.tagName||"checkbox"!==a.type&&"radio"!==a.type?a.addEventListener("input",this.checkDependencies.bind(this)):a.addEventListener("change",this.checkDependencies.bind(this)))}},checkDependencies:function(){var e=[];h.forEach(function(t,i){var n=elById(i);if(null===n)return void e.push(n);for(var o=0,r=t.length;o<r;o++)if(!t[o].checkDependency())return void this._hide(n);this._show(n)}.bind(this));for(var t=0,i=e.length;t<i;t++)h.delete(e.id);this.checkContainers()},addContainerCheckCallback:function(e){if("function"!=typeof e)throw new TypeError("Expected a valid callback for parameter 'callback'.");i.add("com.woltlab.wcf.form.builder.dependency","checkContainers",e)},checkContainers:function(){if(!0===s)return void(l=!0);s=!0,l=!1,i.fire("com.woltlab.wcf.form.builder.dependency","checkContainers"),s=!1,l&&this.checkContainers()},isHiddenByDependencies:function(e){if(c.has(e))return!0;var t=!1;return c.forEach(function(i){r.contains(i,e)&&(t=!0)}),t},register:function(e){var t=elById(e);if(null===t)throw new Error("Unknown element with id '"+e+"'");if(d.has(t))throw new Error("Form with id '"+e+"' has already been registered.");d.add(t)},unregister:function(e){var t=elById(e);if(null===t)throw new Error("Unknown element with id '"+e+"'");if(!d.has(t))throw new Error("Form with id '"+e+"' has not been registered.");d.delete(t),c.forEach(function(e){t.contains(e)&&c.delete(e)}),h.forEach(function(e,i){t.contains(elById(i))&&h.delete(i);for(var n=0,o=e.length;n<o;n++)for(var r=e[n].getFields(),a=0,o=r.length;a<o;a++){var s=r[a];u.delete(s.id),f.delete(s)}})}}}),define("WoltLabSuite/Core/Form/Builder/Field/Field",[],function(){"use strict";function e(e){this.init(e)}return e.prototype={init:function(e){this._fieldId=e,this._readField()},_getData:function(){throw new Error("Missing implementation of WoltLabSuite/Core/Form/Builder/Field/Field._getData!")},_readField:function(){if(this._field=elById(this._fieldId),null===this._field)throw new Error("Unknown field with id '"+this._fieldId+"'.")},destroy:function(){},getData:function(){return Promise.resolve(this._getData())},getId:function(){return this._fieldId}},e}),define("WoltLabSuite/Core/Form/Builder/Manager",["Core","Dictionary","./Field/Dependency/Manager","./Field/Field"],function(e,t,i,n){"use strict";var o=new t,r=new t;return{getData:function(t){if(!this.hasForm(t))throw new Error("Unknown form with id '"+t+"'.");var i=[];return o.get(t).forEach(function(e){var t=e.getData();if(!(t instanceof Promise))throw new TypeError("Data for field with id '"+e.getId()+"' is no promise.");i.push(t)}),Promise.all(i).then(function(t){for(var i={},n=0,o=t.length;n<o;n++)i=e.extend(i,t[n]);return i})},getForm:function(e){if(!this.hasForm(e))throw new Error("Unknown form with id '"+e+"'.");return r.get(e)},hasField:function(e,t){if(!this.hasForm(e))throw new Error("Unknown form with id '"+e+"'.");return o.get(e).has(t)},hasForm:function(e){return r.has(e)},registerField:function(e,t){if(!this.hasForm(e))throw new Error("Unknown form with id '"+e+"'.");if(!(t instanceof n))throw new Error("Add field is no instance of 'WoltLabSuite/Core/Form/Builder/Field/Field'.");var i=t.getId();if(this.hasField(e,i))throw new Error("Form field with id '"+i+"' has already been registered for form with id '"+i+"'.");o.get(e).set(i,t)},registerForm:function(e){if(this.hasForm(e))throw new Error("Form with id '"+e+"' has already been registered.");var i=elById(e);if(null===i)throw new Error("Unknown form with id '"+e+"'.");r.set(e,i),o.set(e,new t)},unregisterForm:function(e){if(!this.hasForm(e))throw new Error("Unknown form with id '"+e+"'.");r.delete(e),o.get(e).forEach(function(e){e.destroy()}),o.delete(e),i.unregister(e)}}}),define("WoltLabSuite/Core/Form/Builder/Dialog",["Ajax","Core","./Manager","Ui/Dialog"],function(e,t,i,n){"use strict";function o(e,t,i,n){this.init(e,t,i,n)}return o.prototype={init:function(e,i,n,o){this._dialogId=e,this._className=i,this._actionName=n,this._options=t.extend({actionParameters:{},destroyOnClose:!1,usesDboAction:this._className.match(/\w+\\data\\/)},o),this._options.dialog=t.extend(this._options.dialog||{},{onClose:this._dialogOnClose.bind(this)}),this._formId="",this._dialogContent=""},_ajaxSetup:function(){var e={data:{actionName:this._actionName,className:this._className,parameters:this._options.actionParameters}};return this._options.usesDboAction||(e.url="index.php?ajax-invoke/&t="+SECURITY_TOKEN,e.withCredentials=!0),e},_ajaxSuccess:function(e){switch(e.actionName){case this._actionName:if(void 0===e.returnValues)throw new Error("Missing return data.");if(void 0===e.returnValues.dialog)throw new Error("Missing dialog template in return data.");if(void 0===e.returnValues.formId)throw new Error("Missing form id in return data.");this._openDialogContent(e.returnValues.formId,e.returnValues.dialog);break;case this._options.submitActionName:if(e.returnValues&&e.returnValues.formId&&e.returnValues.dialog){if(e.returnValues.formId!==this._formId)throw new Error("Mismatch between form ids: expected '"+this._formId+"' but got '"+e.returnValues.formId+"'.");this._openDialogContent(e.returnValues.formId,e.returnValues.dialog)}else this.destroy(),"function"==typeof this._options.successCallback&&this._options.successCallback(e.returnValues||{});break;default:throw new Error("Cannot handle action '"+e.actionName+"'.")}},_closeDialog:function(){n.close(this)},_dialogOnClose:function(){this._options.destroyOnClose&&this.destroy()},_dialogSetup:function(){return{id:this._dialogId,options:this._options.dialog,source:this._dialogContent}},_dialogSubmit:function(){this.getData().then(this._submitForm.bind(this))},_openDialogContent:function(e,t){this.destroy(!0),this._formId=e,this._dialogContent=t;var i=n.open(this,this._dialogContent),o=elBySel("button[data-type=cancel]",i.content);null===o||elDataBool(o,"has-event-listener")||(o.addEventListener("click",this._closeDialog.bind(this)),elData(o,"has-event-listener",1))},_submitForm:function(t){var i=elBySel("button[data-type=submit]",n.getDialog(this).content);"function"==typeof this._options.onSubmit?this._options.onSubmit(t,i):"string"==typeof this._options.submitActionName&&(i.disabled=!0,e.api(this,{actionName:this._options.submitActionName,parameters:{data:t,formId:this._formId}}))},destroy:function(e){""!==this._formId&&(i.hasForm(this._formId)&&i.unregisterForm(this._formId),!0!==e&&n.destroy(this))},getData:function(){if(""===this._formId)throw new Error("Form has not been requested yet.");return i.getData(this._formId)},open:function(){n.getDialog(this._dialogId)?n.openStatic(this._dialogId):e.api(this)}},o}),define("WoltLabSuite/Core/Media/Manager/Search",["Ajax","Core","Dom/Traverse","Dom/Util","EventKey","Language","Ui/SimpleDropdown"],function(e,t,i,n,o,r,a){"use strict";var s=function(){};return s.prototype={_ajaxSetup:function(){},_ajaxSuccess:function(){},_cancelSearch:function(){},_keyPress:function(){},_search:function(){},hideSearch:function(){},resetSearch:function(){},showSearch:function(){}},s}),define("WoltLabSuite/Core/Media/Manager/Base",["Core","Dictionary","Dom/ChangeListener","Dom/Traverse","Dom/Util","EventHandler","Language","List","Permission","Ui/Dialog","Ui/Notification","WoltLabSuite/Core/Controller/Clipboard","WoltLabSuite/Core/Media/Editor","WoltLabSuite/Core/Media/Upload","WoltLabSuite/Core/Media/Manager/Search","StringUtil","WoltLabSuite/Core/Ui/Pagination","WoltLabSuite/Core/Media/Clipboard"],function(e,t,i,n,o,r,a,s,l,c,u,d,h,f,p,g,m,v){"use strict";var b=function(){};return b.prototype={_addButtonEventListeners:function(){},_click:function(){},_dialogClose:function(){},_dialogInit:function(){},_dialogSetup:function(){},_dialogShow:function(){},_editMedia:function(){},_editorClose:function(){},_editorSuccess:function(){},_removeClipboardCheckboxes:function(){},_setMedia:function(){},addMedia:function(){},clipboardDeleteMedia:function(){},getDialog:function(){},getMode:function(){},getOption:function(){},removeMedia:function(){},resetMedia:function(){},setMedia:function(){},setupMediaElement:function(){}},b}),define("WoltLabSuite/Core/Media/Manager/Editor",["Core","Dictionary","Dom/Traverse","EventHandler","Language","Permission","Ui/Dialog","WoltLabSuite/Core/Controller/Clipboard","WoltLabSuite/Core/Media/Manager/Base"],function(e,t,i,n,o,r,a,s,l){"use strict";var c=function(){};return c.prototype={_addButtonEventListeners:function(){},_buildInsertDialog:function(){},_click:function(){},_getInsertDialogId:function(){},_getThumbnailSizes:function(){},_insertMedia:function(){},_insertMediaGallery:function(){},_insertMediaItem:function(){},_openInsertDialog:function(){},insertMedia:function(){},getMode:function(){},setupMediaElement:function(){},_dialogClose:function(){},_dialogInit:function(){},_dialogSetup:function(){},_dialogShow:function(){},_editMedia:function(){},_editorClose:function(){},_editorSuccess:function(){},_removeClipboardCheckboxes:function(){},_setMedia:function(){},addMedia:function(){},clipboardInsertMedia:function(){},getDialog:function(){},getOption:function(){},removeMedia:function(){},resetMedia:function(){},setMedia:function(){}},c}),define("WoltLabSuite/Core/Media/Manager/Select",["Core","Dom/Traverse","Dom/Util","Language","ObjectMap","Ui/Dialog","WoltLabSuite/Core/FileUtil","WoltLabSuite/Core/Media/Manager/Base"],function(e,t,i,n,o,r,a,s){"use strict";var l=function(){};return l.prototype={_addButtonEventListeners:function(){},_chooseMedia:function(){},_click:function(){},getMode:function(){},setupMediaElement:function(){},_removeMedia:function(){},_clipboardAction:function(){},_dialogClose:function(){},_dialogInit:function(){},_dialogSetup:function(){},_dialogShow:function(){},_editMedia:function(){},_editorClose:function(){},_editorSuccess:function(){},_removeClipboardCheckboxes:function(){},_setMedia:function(){},addMedia:function(){},getDialog:function(){},getOption:function(){},removeMedia:function(){},resetMedia:function(){},setMedia:function(){}},l}),define("WoltLabSuite/Core/Ui/Search/Input",["Ajax","Core","EventKey","Dom/Util","Ui/SimpleDropdown"],function(e,t,i,n,o){"use strict";function r(e,t){this.init(e,t)}return r.prototype={init:function(e,i){if(this._element=e,!(this._element instanceof Element))throw new TypeError("Expected a valid DOM element.");if("INPUT"!==this._element.nodeName||"search"!==this._element.type&&"text"!==this._element.type)throw new Error('Expected an input[type="text"].');this._activeItem=null,this._dropdownContainerId="",this._lastValue="",this._list=null,this._request=null,this._timerDelay=null,this._options=t.extend({ajax:{actionName:"getSearchResultList",className:"",interfaceName:"wcf\\data\\ISearchAction"},callbackDropdownInit:null,callbackSelect:null,delay:500,excludedSearchValues:[],minLength:3,noResultPlaceholder:"",preventSubmit:!1},i),elAttr(this._element,"autocomplete","off"),this._element.addEventListener("keydown",this._keydown.bind(this)),this._element.addEventListener("keyup",this._keyup.bind(this))},addExcludedSearchValues:function(e){-1===this._options.excludedSearchValues.indexOf(e)&&this._options.excludedSearchValues.push(e)},removeExcludedSearchValues:function(e){var t=this._options.excludedSearchValues.indexOf(e);-1!==t&&this._options.excludedSearchValues.splice(t,1)},_keydown:function(e){(null!==this._activeItem&&o.isOpen(this._dropdownContainerId)||this._options.preventSubmit)&&i.Enter(e)&&e.preventDefault(),(i.ArrowUp(e)||i.ArrowDown(e)||i.Escape(e))&&e.preventDefault()},_keyup:function(e){if(null!==this._activeItem)if(o.isOpen(this._dropdownContainerId)){if(i.ArrowUp(e))return e.preventDefault(),this._keyboardPreviousItem();if(i.ArrowDown(e))return e.preventDefault(),this._keyboardNextItem();if(i.Enter(e))return e.preventDefault(),this._keyboardSelectItem()}else this._activeItem=null;if(i.Escape(e))return void o.close(this._dropdownContainerId);var t=this._element.value.trim();if(this._lastValue!==t){if(this._lastValue=t,t.length<this._options.minLength)return void(this._dropdownContainerId&&(o.close(this._dropdownContainerId),this._activeItem=null));this._options.delay?(null!==this._timerDelay&&window.clearTimeout(this._timerDelay),this._timerDelay=window.setTimeout(function(){this._search(t)}.bind(this),this._options.delay)):this._search(t)}},_search:function(t){this._request&&this._request.abortPrevious(),this._request=e.api(this,this._getParameters(t))},_getParameters:function(e){return{parameters:{data:{excludedSearchValues:this._options.excludedSearchValues,searchString:e}}}},_keyboardNextItem:function(){this._activeItem.classList.remove("active"),this._activeItem.nextElementSibling?this._activeItem=this._activeItem.nextElementSibling:this._activeItem=this._list.children[0],this._activeItem.classList.add("active")},_keyboardPreviousItem:function(){this._activeItem.classList.remove("active"),this._activeItem.previousElementSibling?this._activeItem=this._activeItem.previousElementSibling:this._activeItem=this._list.children[this._list.childElementCount-1],this._activeItem.classList.add("active")},_keyboardSelectItem:function(){this._selectItem(this._activeItem)},_clickSelectItem:function(e){this._selectItem(e.currentTarget)},_selectItem:function(e){this._options.callbackSelect&&!1===this._options.callbackSelect(e)?this._element.value="":this._element.value=elData(e,"label"),this._activeItem=null,o.close(this._dropdownContainerId)},_ajaxSuccess:function(e){var t=!1;if(null===this._list?(this._list=elCreate("ul"),this._list.className="dropdownMenu",t=!0,"function"==typeof this._options.callbackDropdownInit&&this._options.callbackDropdownInit(this._list)):this._list.innerHTML="","object"==typeof e.returnValues){var i,r=this._clickSelectItem.bind(this);for(var a in e.returnValues)e.returnValues.hasOwnProperty(a)&&(i=this._createListItem(e.returnValues[a]),i.addEventListener(WCF_CLICK_EVENT,r),this._list.appendChild(i))}t&&(n.insertAfter(this._list,this._element),o.initFragment(this._element.parentNode,this._list),this._dropdownContainerId=n.identify(this._element.parentNode)),this._dropdownContainerId&&(this._activeItem=null,this._list.childElementCount||!1!==this._handleEmptyResult()?(o.open(this._dropdownContainerId,!0),this._list.childElementCount&&~~elData(this._list.children[0],"object-id")&&(this._activeItem=this._list.children[0],this._activeItem.classList.add("active"))):o.close(this._dropdownContainerId))},_handleEmptyResult:function(){if(!this._options.noResultPlaceholder)return!1;var e=elCreate("li");e.className="dropdownText";var t=elCreate("span");return t.textContent=this._options.noResultPlaceholder,e.appendChild(t),this._list.appendChild(e),!0},_createListItem:function(e){var t=elCreate("li");elData(t,"object-id",e.objectID),elData(t,"label",e.label);var i=elCreate("span");return i.textContent=e.label,t.appendChild(i),t},_ajaxSetup:function(){return{data:this._options.ajax}}},r}),define("WoltLabSuite/Core/Ui/User/Search/Input",["Core","WoltLabSuite/Core/Ui/Search/Input"],function(e,t){"use strict";function i(e,t){this.init(e,t)}return e.inherit(i,t,{init:function(t,n){var o=e.isPlainObject(n)&&!0===n.includeUserGroups;n=e.extend({ajax:{className:"wcf\\data\\user\\UserAction",parameters:{data:{
+includeUserGroups:o?1:0}}}},n),i._super.prototype.init.call(this,t,n)},_createListItem:function(e){var t=i._super.prototype._createListItem.call(this,e);elData(t,"type",e.type);var n=elCreate("div");return n.className="box16",n.innerHTML="group"===e.type?'<span class="icon icon16 fa-users"></span>':e.icon,n.appendChild(t.children[0]),t.appendChild(n),t}}),i}),define("WoltLabSuite/Core/Ui/Acl/Simple",["Language","StringUtil","Dom/ChangeListener","WoltLabSuite/Core/Ui/User/Search/Input"],function(e,t,i,n){"use strict";var o=function(){};return o.prototype={init:function(){},_build:function(){},_select:function(){},_removeItem:function(){}},o}),define("WoltLabSuite/Core/Ui/Article/MarkAllAsRead",["Ajax"],function(e){"use strict";return{init:function(){elBySelAll(".markAllAsReadButton",void 0,function(e){e.addEventListener(WCF_CLICK_EVENT,this._click.bind(this))}.bind(this))},_click:function(t){t.preventDefault(),e.api(this)},_ajaxSuccess:function(){var e=elBySel(".mainMenu .active .badge");e&&elRemove(e),elBySelAll(".articleList .newMessageBadge",void 0,elRemove)},_ajaxSetup:function(){return{data:{actionName:"markAllAsRead",className:"wcf\\data\\article\\ArticleAction"}}}}}),define("WoltLabSuite/Core/Ui/Article/Search",["Ajax","EventKey","Language","StringUtil","Dom/Util","Ui/Dialog"],function(e,t,i,n,o,r){"use strict";var a=function(){};return a.prototype={open:function(){},_search:function(){},_click:function(){},_ajaxSuccess:function(){},_ajaxSetup:function(){},_dialogSetup:function(){}},a}),define("WoltLabSuite/Core/Ui/Color/Picker",["Core"],function(e){"use strict";function t(e,t){this.init(e,t)}var i=function(e,t){if("object"==typeof window.WCF&&"function"==typeof window.WCF.ColorPicker)return(i=function(e,t){var i=new window.WCF.ColorPicker(e);return"function"==typeof t.callbackSubmit&&i.setCallbackSubmit(t.callbackSubmit),i})(e,t);0===n.length&&(window.__wcf_bc_colorPickerInit=function(){n.forEach(function(e){i(e[0],e[1])}),window.__wcf_bc_colorPickerInit=void 0,n=[]}),n.push([e,t])},n=[];return t.prototype={init:function(t,n){if(!(t instanceof Element))throw new TypeError("Expected a valid DOM element, use `UiColorPicker.fromSelector()` if you want to use a CSS selector.");this._options=e.extend({callbackSubmit:null},n),i(t,n)}},t.fromSelector=function(e){elBySelAll(e,void 0,function(e){new t(e)})},t}),define("WoltLabSuite/Core/Ui/Comment/Add",["Ajax","Core","EventHandler","Language","Dom/ChangeListener","Dom/Util","Dom/Traverse","Ui/Dialog","Ui/Notification","WoltLabSuite/Core/Ui/Scroll","EventKey","User","WoltLabSuite/Core/Controller/Captcha"],function(e,t,i,n,o,r,a,s,l,c,u,d,h){"use strict";var f=function(){};return f.prototype={init:function(){},_submitGuestDialog:function(){},_submit:function(){},_getParameters:function(){},_validate:function(){},throwError:function(){},_showLoadingOverlay:function(){},_hideLoadingOverlay:function(){},_reset:function(){},_handleError:function(){},_getEditor:function(){},_insertMessage:function(){},_ajaxSuccess:function(){},_ajaxFailure:function(){},_ajaxSetup:function(){},_cancelGuestDialog:function(){}},f}),define("WoltLabSuite/Core/Ui/Comment/Edit",["Ajax","Core","Dictionary","Environment","EventHandler","Language","List","Dom/ChangeListener","Dom/Traverse","Dom/Util","Ui/Notification","Ui/ReusableDropdown","WoltLabSuite/Core/Ui/Scroll"],function(e,t,i,n,o,r,a,s,l,c,u,d,h){"use strict";var f=function(){};return f.prototype={init:function(){},rebuild:function(){},_click:function(){},_prepare:function(){},_showEditor:function(){},_restoreMessage:function(){},_save:function(){},_validate:function(){},throwError:function(){},_showMessage:function(){},_hideEditor:function(){},_restoreEditor:function(){},_destroyEditor:function(){},_getEditorId:function(){},_getObjectId:function(){},_ajaxFailure:function(){},_ajaxSuccess:function(){},_ajaxSetup:function(){}},f}),define("WoltLabSuite/Core/Ui/Dropdown/Builder",["Core","Ui/SimpleDropdown"],function(e,t){"use strict";function i(e){if(!(e instanceof HTMLUListElement))throw new TypeError("Expected a reference to an <ul> element.");if(!e.classList.contains("dropdownMenu"))throw new Error("List does not appear to be a dropdown menu.")}function n(t){var i=elCreate("li");if("divider"===t)return i.className="dropdownDivider",i;"string"==typeof t.identifier&&elData(i,"identifier",t.identifier);var n=elCreate("a");if(n.href="string"==typeof t.href?t.href:"#","function"==typeof t.callback)n.addEventListener(WCF_CLICK_EVENT,function(e){e.preventDefault(),t.callback(n)});else if("#"===n.getAttribute("href"))throw new Error("Expected either a `href` value or a `callback`.");if(t.hasOwnProperty("attributes")&&e.isPlainObject(t.attributes))for(var r in t.attributes)t.attributes.hasOwnProperty(r)&&elData(n,r,t.attributes[r]);if(i.appendChild(n),void 0!==t.icon&&e.isPlainObject(t.icon)){if("string"!=typeof t.icon.name)throw new TypeError("Expected a valid icon name.");var a=16;"number"==typeof t.icon.size&&-1!==o.indexOf(~~t.icon.size)&&(a=~~t.icon.size);var s=elCreate("span");s.className="icon icon"+a+" fa-"+t.icon.name,n.appendChild(s)}var l="string"==typeof t.label?t.label.trim():"",c="string"==typeof t.labelHtml?t.labelHtml.trim():"";if(""===l&&""===c)throw new TypeError("Expected either a label or a `labelHtml`.");var u=elCreate("span");return u[l?"textContent":"innerHTML"]=l||c,n.appendChild(document.createTextNode(" ")),n.appendChild(u),i}var o=[16,24,32,48,64,96,144];return{create:function(e,t){var i=elCreate("ul");return i.className="dropdownMenu","string"==typeof t&&elData(i,"identifier",t),Array.isArray(e)&&e.length>0&&this.appendItems(i,e),i},buildItem:function(e){return n(e)},appendItem:function(e,t){i(e),e.appendChild(n(t))},appendItems:function(e,t){if(i(e),!Array.isArray(t))throw new TypeError("Expected an array of items.");var o=t.length;if(0===o)throw new Error("Expected a non-empty list of items.");if(1===o)this.appendItem(e,t[0]);else{for(var r=document.createDocumentFragment(),a=0;a<o;a++)r.appendChild(n(t[a]));e.appendChild(r)}},setItems:function(e,t){i(e),e.innerHTML="",this.appendItems(e,t)},attach:function(e,n){i(e),t.initFragment(n,e),n.addEventListener(WCF_CLICK_EVENT,function(e){e.preventDefault(),e.stopPropagation(),t.toggleDropdown(n.id)})},divider:function(){return"divider"}}}),define("WoltLabSuite/Core/Ui/File/Delete",["Ajax","Core","Dom/ChangeListener","Language","Dom/Util","Dom/Traverse","Dictionary"],function(e,t,i,n,o,r,a){"use strict";function s(e,t,i,n){if(this._isSingleImagePreview=i,this._uploadHandler=n,this._buttonContainer=elById(e),null===this._buttonContainer)throw new Error("Element id '"+e+"' is unknown.");if(this._target=elById(t),null===t)throw new Error("Element id '"+t+"' is unknown.");if(this._containers=new a,this._internalId=elData(this._target,"internal-id"),!this._internalId)throw new Error("InternalId is unknown.");this.rebuild()}return s.prototype={_createButtons:function(){for(var e,t,n,o=elBySelAll("li.uploadedFile",this._target),r=!1,a=0,s=o.length;a<s;a++)e=o[a],n=elData(e,"unique-file-id"),this._containers.has(n)||(t={uniqueFileId:n,element:e},this._containers.set(n,t),this._initDeleteButton(e,t),r=!0);r&&i.trigger()},_initDeleteButton:function(e,t){var i=elBySel(".buttonGroup",e);if(null===i)throw new Error("Button group in '"+targetId+"' is unknown.");var o=elCreate("li"),r=elCreate("span");r.classList="button jsDeleteButton small",r.textContent=n.get("wcf.global.button.delete"),o.appendChild(r),i.appendChild(o),o.addEventListener(WCF_CLICK_EVENT,this._delete.bind(this,t.uniqueFileId))},_delete:function(t){e.api(this,{uniqueFileId:t,internalId:this._internalId})},rebuild:function(){if(this._isSingleImagePreview){var e=elBySel("img",this._target);if(null!==e){var t=elData(e,"unique-file-id");if(!this._containers.has(t)){var i={uniqueFileId:t,element:e};this._containers.set(t,i),this._deleteButton=elCreate("p"),this._deleteButton.className="button deleteButton";var o=elCreate("span");o.textContent=n.get("wcf.global.button.delete"),this._deleteButton.appendChild(o),this._buttonContainer.appendChild(this._deleteButton),this._deleteButton.addEventListener(WCF_CLICK_EVENT,this._delete.bind(this,i.uniqueFileId))}}}else this._createButtons()},_ajaxSuccess:function(e){elRemove(this._containers.get(e.uniqueFileId).element),this._isSingleImagePreview&&(elRemove(this._deleteButton),this._deleteButton=null),this._uploadHandler.checkMaxFiles()},_ajaxSetup:function(){return{url:"index.php?ajax-file-delete/&t="+SECURITY_TOKEN}}},s}),define("WoltLabSuite/Core/Ui/File/Upload",["Core","Language","Dom/Util","WoltLabSuite/Core/Ui/File/Delete","Upload"],function(e,t,i,n,o){"use strict";function r(t,i,o){if(o=o||{},void 0===o.internalId)throw new Error("Missing internal id.");if(this._options=e.extend({name:"__files[]",singleFileRequests:!1,url:"index.php?ajax-file-upload/&t="+SECURITY_TOKEN,imagePreview:!1,maxFiles:null},o),this._options.multiple=null===this._options.maxFiles||this._options.maxFiles>1,this._options.url=this._options.url,0===this._options.url.indexOf("index.php")&&(this._options.url=WSC_API_URL+this._options.url),this._buttonContainer=elById(t),null===this._buttonContainer)throw new Error("Element id '"+t+"' is unknown.");if(this._target=elById(i),null===i)throw new Error("Element id '"+i+"' is unknown.");if(o.multiple&&"UL"!==this._target.nodeName&&"OL"!==this._target.nodeName)throw new Error("Target element has to be list or table body if uploading multiple files is supported.");this._fileElements=[],this._internalFileId=0,this._multiFileUploadIds=[],this._createButton(),this.checkMaxFiles(),this._deleteHandler=new n(t,i,this._options.imagePreview,this)}return e.inherit(r,o,{_createFileElement:function(e){var t=r._super.prototype._createFileElement.call(this,e);t.classList.add("box64","uploadedFile");var i=elBySel("progress",t),n=elCreate("span");n.classList="icon icon64 fa-spinner";var o=t.textContent;t.textContent="",t.append(n);var a=elCreate("div"),s=elCreate("p");s.textContent=o;var l=elCreate("small");l.appendChild(i),a.appendChild(s),a.appendChild(l);var c=elCreate("div");c.appendChild(a);var u=elCreate("ul");return u.classList="buttonGroup",c.appendChild(u),t.append(c),t},_failure:function(e,n,o,r,a){for(var s=0,l=this._fileElements[e].length;s<l;s++){this._fileElements[e][s].classList.add("uploadFailed"),elBySel("small",this._fileElements[e][s]).innerHTML="";var c=elBySel(".icon",this._fileElements[e][s]);c.classList.remove("fa-spinner"),c.classList.add("fa-ban");var u=elCreate("span");u.classList="innerError",u.textContent=t.get("wcf.upload.error.uploadFailed"),i.insertAfter(u,elBySel("small",this._fileElements[e][s]))}throw new Error("Upload failed: "+n.message)},_upload:function(e,t,i){var n=elBySel("small.innerError:not(.innerFileError)",this._buttonContainer.parentNode);return n&&elRemove(n),r._super.prototype._upload.call(this,e,t,i)},_success:function(e,t,n,o,r){for(var a=0,s=this._fileElements[e].length;a<s;a++)if(void 0!==t.files[a])if(this._options.imagePreview){if(null===t.files[a].image)throw new Error("Expect image for uploaded file. None given.");if(elRemove(this._fileElements[e][a]),null!==elBySel("img.previewImage",this._target))elBySel("img.previewImage",this._target).setAttribute("src",t.files[a].image);else{var l=elCreate("img");l.classList.add("previewImage"),l.setAttribute("src",t.files[a].image),l.setAttribute("style","max-width: 100%;"),elData(l,"unique-file-id",t.files[a].uniqueFileId),this._target.appendChild(l)}}else{elData(this._fileElements[e][a],"unique-file-id",t.files[a].uniqueFileId),elBySel("small",this._fileElements[e][a]).textContent=t.files[a].filesize;var c=elBySel(".icon",this._fileElements[e][a]);c.classList.remove("fa-spinner"),c.classList.add("fa-"+t.files[a].icon)}else{if(void 0===t.error[a])throw new Error("Unknown uploaded file for uploadId "+e+".");this._fileElements[e][a].classList.add("uploadFailed"),elBySel("small",this._fileElements[e][a]).innerHTML="";var c=elBySel(".icon",this._fileElements[e][a]);if(c.classList.remove("fa-spinner"),c.classList.add("fa-ban"),null===elBySel(".innerError",this._fileElements[e][a])){var u=elCreate("span");u.classList="innerError",u.textContent=t.error[a].errorMessage,i.insertAfter(u,elBySel("small",this._fileElements[e][a]))}else elBySel(".innerError",this._fileElements[e][a]).textContent=t.error[a].errorMessage}this._deleteHandler.rebuild(),this.checkMaxFiles()},_getFormData:function(){return{internalId:this._options.internalId}},validateUpload:function(e){if(null===this._options.maxFiles||e.length+this.countFiles()<=this._options.maxFiles)return!0;var n=elBySel("small.innerError:not(.innerFileError)",this._buttonContainer.parentNode);return null===n&&(n=elCreate("small"),n.classList="innerError",i.insertAfter(n,this._buttonContainer)),n.textContent=t.get("wcf.upload.error.reachedRemainingLimit",{maxFiles:this._options.maxFiles-this.countFiles()}),!1},countFiles:function(){return this._options.imagePreview?null!==elBySel("img",this._target)?1:0:this._target.childElementCount},checkMaxFiles:function(){null!==this._options.maxFiles&&this.countFiles()>=this._options.maxFiles?elHide(this._button):elShow(this._button)}}),r}),define("WoltLabSuite/Core/Ui/ItemList/Filter",["Core","EventKey","Language","List","StringUtil","Dom/Util","Ui/SimpleDropdown"],function(e,t,i,n,o,r,a){"use strict";var s=function(){};return s.prototype={init:function(){},_buildItems:function(){},_prepareItem:function(){},_keyup:function(){},_toggleVisibility:function(){},_setupVisibilityFilter:function(){},_setVisibility:function(){}},s}),define("WoltLabSuite/Core/Ui/ItemList/Static",["Core","Dictionary","Language","Dom/Traverse","EventKey","Ui/SimpleDropdown"],function(e,t,i,n,o,r){"use strict";var a="",s=new t,l=!1,c=null,u=null,d=null,h=null,f=null,p=null;return{init:function(t,i,o){var a=elById(t);if(null===a)throw new Error("Expected a valid element id, '"+t+"' is invalid.");if(s.has(t)){var l=s.get(t);for(var c in l)if(l.hasOwnProperty(c)){var u=l[c];u instanceof Element&&u.parentNode&&elRemove(u)}r.destroy(t),s.delete(t)}o=e.extend({maxItems:-1,maxLength:-1,isCSV:!1,callbackChange:null,callbackSubmit:null,submitFieldName:""},o);var d=n.parentByTag(a,"FORM");if(null!==d&&!1===o.isCSV){if(!o.submitFieldName.length&&"function"!=typeof o.callbackSubmit)throw new Error("Expected a valid function for option 'callbackSubmit', a non-empty value for option 'submitFieldName' or enabling the option 'submitFieldCSV'.");d.addEventListener("submit",function(){var e=this.getValues(t);if(o.submitFieldName.length)for(var i,n=0,r=e.length;n<r;n++)i=elCreate("input"),i.type="hidden",i.name=o.submitFieldName.replace(/{$objectId}/,e[n].objectId),i.value=e[n].value,d.appendChild(i);else o.callbackSubmit(d,e)}.bind(this))}this._setup();var h=this._createUI(a,o);if(s.set(t,{dropdownMenu:null,element:h.element,list:h.list,listItem:h.element.parentNode,options:o,shadow:h.shadow}),i=h.values.length?h.values:i,Array.isArray(i))for(var f,p=0,g=i.length;p<g;p++)f=i[p],"string"==typeof f&&(f={objectId:0,value:f}),this._addItem(t,f)},getValues:function(e){if(!s.has(e))throw new Error("Element id '"+e+"' is unknown.");var t=s.get(e),i=[];return elBySelAll(".item > span",t.list,function(e){i.push({objectId:~~elData(e,"object-id"),value:e.textContent})}),i},setValues:function(e,t){if(!s.has(e))throw new Error("Element id '"+e+"' is unknown.");var i,o,r=s.get(e),a=n.childrenByClass(r.list,"item");for(i=0,o=a.length;i<o;i++)this._removeItem(null,a[i],!0);for(i=0,o=t.length;i<o;i++)this._addItem(e,t[i])},_setup:function(){l||(l=!0,c=this._keyDown.bind(this),u=this._keyPress.bind(this),d=this._keyUp.bind(this),h=this._paste.bind(this),f=this._removeItem.bind(this),p=this._blur.bind(this))},_createUI:function(e,t){var i=elCreate("ol");i.className="inputItemList"+(e.disabled?" disabled":""),elData(i,"element-id",e.id),i.addEventListener(WCF_CLICK_EVENT,function(t){t.target===i&&e.focus()});var n=elCreate("li");n.className="input",i.appendChild(n),e.addEventListener("keydown",c),e.addEventListener("keypress",u),e.addEventListener("keyup",d),e.addEventListener("paste",h),e.addEventListener("blur",p),e.parentNode.insertBefore(i,e),n.appendChild(e),-1!==t.maxLength&&elAttr(e,"maxLength",t.maxLength);var o=null,r=[];if(t.isCSV){o=elCreate("input"),o.className="itemListInputShadow",o.type="hidden",o.name=e.name,e.removeAttribute("name"),i.parentNode.insertBefore(o,i);for(var a,s=e.value.split(","),l=0,f=s.length;l<f;l++)a=s[l].trim(),a.length&&r.push(a);if("TEXTAREA"===e.nodeName){var g=elCreate("input");g.type="text",e.parentNode.insertBefore(g,e),g.id=e.id,elRemove(e),e=g}}return{element:e,list:i,shadow:o,values:r}},_handleLimit:function(e){var t=s.get(e);-1!==t.options.maxItems&&(t.list.childElementCount-1<t.options.maxItems?t.element.disabled&&(t.element.disabled=!1,t.element.removeAttribute("placeholder")):t.element.disabled||(t.element.disabled=!0,elAttr(t.element,"placeholder",i.get("wcf.global.form.input.maxItems"))))},_keyDown:function(e){var t=e.currentTarget,i=t.parentNode.previousElementSibling;a=t.id,8===e.keyCode?0===t.value.length&&null!==i&&(i.classList.contains("active")?this._removeItem(null,i):i.classList.add("active")):27===e.keyCode&&null!==i&&i.classList.contains("active")&&i.classList.remove("active")},_keyPress:function(e){if(o.Enter(e)||o.Comma(e)){e.preventDefault();var t=e.currentTarget.value.trim();t.length&&this._addItem(e.currentTarget.id,{objectId:0,value:t})}},_paste:function(e){var t="";t="object"==typeof window.clipboardData?window.clipboardData.getData("Text"):e.clipboardData.getData("text/plain"),t.split(/,/).forEach(function(t){t=t.trim(),0!==t.length&&this._addItem(e.currentTarget.id,{objectId:0,value:t})}.bind(this)),e.preventDefault()},_keyUp:function(e){var t=e.currentTarget;if(t.value.length>0){var i=t.parentNode.previousElementSibling;null!==i&&i.classList.remove("active")}},_addItem:function(e,t){var i=s.get(e),n=elCreate("li");n.className="item";var o=elCreate("span");if(o.className="content",elData(o,"object-id",t.objectId),o.textContent=t.value,n.appendChild(o),!i.element.disabled){var r=elCreate("a");r.className="icon icon16 fa-times",r.addEventListener(WCF_CLICK_EVENT,f),n.appendChild(r)}i.list.insertBefore(n,i.listItem),i.element.value="",i.element.disabled||this._handleLimit(e);var a=this._syncShadow(i);"function"==typeof i.options.callbackChange&&(null===a&&(a=this.getValues(e)),i.options.callbackChange(e,a))},_removeItem:function(e,t,i){t=null===e?t:e.currentTarget.parentNode;var n=t.parentNode,o=elData(n,"element-id"),r=s.get(o);n.removeChild(t),i||r.element.focus(),this._handleLimit(o);var a=this._syncShadow(r);"function"==typeof r.options.callbackChange&&(null===a&&(a=this.getValues(o)),r.options.callbackChange(o,a))},_syncShadow:function(e){if(!e.options.isCSV)return null;for(var t="",i=this.getValues(e.element.id),n=0,o=i.length;n<o;n++)t+=(t.length?",":"")+i[n].value;return e.shadow.value=t,i},_blur:function(e){var t=(s.get(e.currentTarget.id),e.currentTarget);window.setTimeout(function(){var e=t.value.trim();e.length&&this._addItem(t.id,{objectId:0,value:e})}.bind(this),100)}}}),define("WoltLabSuite/Core/Ui/ItemList/User",["WoltLabSuite/Core/Ui/ItemList"],function(e){"use strict";var t=function(){};return t.prototype={init:function(){},getValues:function(){}},t}),define("WoltLabSuite/Core/Ui/User/List",["Ajax","Core","Dictionary","Dom/Util","Ui/Dialog","WoltLabSuite/Core/Ui/Pagination"],function(e,t,i,n,o,r){"use strict";function a(e){this.init(e)}return a.prototype={init:function(e){this._cache=new i,this._pageCount=0,this._pageNo=1,this._options=t.extend({className:"",dialogTitle:"",parameters:{}},e)},open:function(){this._pageNo=1,this._showPage()},_showPage:function(t){if("number"==typeof t&&(this._pageNo=~~t),0!==this._pageCount&&(this._pageNo<1||this._pageNo>this._pageCount))throw new RangeError("pageNo must be between 1 and "+this._pageCount+" ("+this._pageNo+" given).");if(this._cache.has(this._pageNo)){var i=o.open(this,this._cache.get(this._pageNo));if(this._pageCount>1){var n=elBySel(".jsPagination",i.content);null!==n&&new r(n,{activePage:this._pageNo,maxPage:this._pageCount,callbackSwitch:this._showPage.bind(this)});var a=i.content.parentNode;a.scrollTop>0&&(a.scrollTop=0)}}else this._options.parameters.pageNo=this._pageNo,e.api(this,{parameters:this._options.parameters})},_ajaxSuccess:function(e){void 0!==e.returnValues.pageCount&&(this._pageCount=~~e.returnValues.pageCount),this._cache.set(this._pageNo,e.returnValues.template),this._showPage()},_ajaxSetup:function(){return{data:{actionName:"getGroupedUserList",className:this._options.className,interfaceName:"wcf\\data\\IGroupedUserListAction"}}},_dialogSetup:function(){return{id:n.getUniqueId(),options:{title:this._options.dialogTitle},source:null}}},a}),define("WoltLabSuite/Core/Ui/Reaction/CountButtons",["Ajax","Core","Dictionary","Language","ObjectMap","StringUtil","Dom/ChangeListener","Dom/Util","Ui/Dialog"],function(e,t,i,n,o,r,a,s,l){"use strict";function c(e,t){this.init(e,t)}return c.prototype={init:function(e,n){if(""===n.containerSelector)throw new Error("[WoltLabSuite/Core/Ui/Reaction/CountButtons] Expected a non-empty string for option 'containerSelector'.");this._containers=new i,this._objects=new i,this._objectType=e,this._options=t.extend({summaryListSelector:".reactionSummaryList",containerSelector:"",isSingleItem:!1,parameters:{data:{}}},n),this.initContainers(n,e),a.add("WoltLabSuite/Core/Ui/Reaction/CountButtons-"+e,this.initContainers.bind(this))},initContainers:function(){for(var e,t,i=elBySelAll(this._options.containerSelector),n=!1,o=0,r=i.length;o<r;o++)if(e=i[o],!this._containers.has(s.identify(e))){if(t={reactButton:null,summary:null,objectId:~~elData(e,"object-id"),element:e},this._containers.set(s.identify(e),t),this._initReactionCountButtons(e,t),this._objects.has(~~elData(e,"object-id")))var l=this._objects.get(~~elData(e,"object-id"));else var l=[];l.push(t),this._objects.set(~~elData(e,"object-id"),l),n=!0}n&&a.trigger()},updateCountButtons:function(e,t){var i=!1;this._objects.get(e).forEach(function(n){for(var o=elBySel(this._options.summaryListSelector,n.element),a={},s=elBySelAll("li",o),l=0,c=s.length;l<c;l++)void 0!==t[elData(s[l],"reaction-type-id")]?a[elData(s[l],"reaction-type-id")]=s[l]:elRemove(s[l]);Object.keys(t).forEach(function(n){if(void 0!==a[n]){elBySel(".reactionCount",a[n]).innerHTML=r.shortUnit(t[n])}else if(void 0!==REACTION_TYPES[n]){var s=elCreate("li");s.className="reactCountButton",elData(s,"reaction-type-id",n);var l=elCreate("span");l.className="reactionCount",l.innerHTML=r.shortUnit(t[n]),s.appendChild(l),s.innerHTML=s.innerHTML+REACTION_TYPES[n].renderedIcon,o.appendChild(s),this._initReactionCountButton(s,e),i=!0}},this)}.bind(this)),i&&a.trigger()},_initReactionCountButtons:function(e,t){if(this._options.isSingleItem)var i=elBySel(this._options.summaryListSelector);else var i=elBySel(this._options.summaryListSelector,e);if(null!==i)for(var n=elBySelAll("li",i),o=0,r=n.length;o<r;o++)this._initReactionCountButton(n[o],t.objectId)},_initReactionCountButton:function(e,t){e.addEventListener(WCF_CLICK_EVENT,this._showReactionOverlay.bind(this,t))},_showReactionOverlay:function(e){this._currentObjectId=e,this._showOverlay()},_showOverlay:function(){this._options.parameters.data.containerID=this._objectType+"-"+this._currentObjectId,this._options.parameters.data.objectID=this._currentObjectId,this._options.parameters.data.objectType=this._objectType,e.api(this,{parameters:this._options.parameters})},_ajaxSuccess:function(e){l.open(this,e.returnValues.template),l.setTitle("userReactionOverlay-"+this._objectType,e.returnValues.title)},_ajaxSetup:function(){return{data:{actionName:"getReactionDetails",className:"\\wcf\\data\\reaction\\ReactionAction"}}},_dialogSetup:function(){return{id:"userReactionOverlay-"+this._objectType,options:{title:""},source:null}}},c}),define("WoltLabSuite/Core/Ui/Reaction/Handler",["Ajax","Core","Dictionary","Language","ObjectMap","StringUtil","Dom/ChangeListener","Dom/Util","Ui/Dialog","WoltLabSuite/Core/Ui/User/List","User","WoltLabSuite/Core/Ui/Reaction/CountButtons","Ui/Alignment","Ui/CloseOverlay","Ui/Screen"],function(e,t,i,n,o,r,a,s,l,c,u,d,h,f,p){"use strict";function g(e,t){this.init(e,t)}return g.prototype={init:function(e,n){if(""===n.containerSelector)throw new Error("[WoltLabSuite/Core/Ui/Reaction/Handler] Expected a non-empty string for option 'containerSelector'.");this._containers=new i,this._details=new o,this._objectType=e,this._cache=new i,this._objects=new i,this._popoverCurrentObjectId=0,this._popover=null,this._options=t.extend({buttonSelector:".reactButton",containerSelector:"",isButtonGroupNavigation:!1,isSingleItem:!1,parameters:{data:{}}},n),this.initReactButtons(n,e),this.countButtons=new d(this._objectType,this._options),a.add("WoltLabSuite/Core/Ui/Reaction/Handler-"+e,this.initReactButtons.bind(this)),f.add("WoltLabSuite/Core/Ui/Reaction/Handler",this._closePopover.bind(this))},initReactButtons:function(){for(var e,t,i=elBySelAll(this._options.containerSelector),n=!1,o=0,r=i.length;o<r;o++)if(e=i[o],!this._containers.has(s.identify(e))){if(t={reactButton:null,objectId:~~elData(e,"object-id"),element:e},this._containers.set(s.identify(e),t),this._initReactButton(e,t),this._objects.has(~~elData(e,"object-id")))var l=this._objects.get(~~elData(e,"object-id"));else var l=[];l.push(t),this._objects.set(~~elData(e,"object-id"),l),n=!0}n&&a.trigger()},_initReactButton:function(e,t){if(this._options.isSingleItem?t.reactButton=elBySel(this._options.buttonSelector):t.reactButton=elBySel(this._options.buttonSelector,e),null!==t.reactButton&&0!==t.reactButton.length){if(1===Object.keys(REACTION_TYPES).length){var i=REACTION_TYPES[Object.keys(REACTION_TYPES)[0]];t.reactButton.title=i.title;elBySel(".invisible",t.reactButton).innerText=i.title}t.reactButton.closest(".messageFooterGroup > .jsMobileNavigation")&&p.on("screen-sm-down",{match:this._enableMobileView.bind(this,t.reactButton,t.objectId),unmatch:this._disableMobileView.bind(this,t.reactButton,t.objectId),setup:this._setupMobileView.bind(this,t.reactButton,t.objectId)}),t.reactButton.addEventListener(WCF_CLICK_EVENT,this._toggleReactPopover.bind(this,t.objectId,t.reactButton))}},_enableMobileView:function(e){var t=e.closest(".messageFooterGroup");elShow(elBySel(".mobileReactButton",t))},_disableMobileView:function(e){var t=e.closest(".messageFooterGroup");elHide(elBySel(".mobileReactButton",t))},_setupMobileView:function(e,t){var i=e.closest(".messageFooterGroup"),n=elCreate("button");n.classList="mobileReactButton",n.innerHTML=e.innerHTML,n.addEventListener(WCF_CLICK_EVENT,this._toggleReactPopover.bind(this,t,n)),i.appendChild(n)},_updateReactButton:function(e,t){this._objects.get(e).forEach(function(e){t?(e.reactButton.classList.add("active"),elData(e.reactButton,"reaction-type-id",t)):(elData(e.reactButton,"reaction-type-id",0),e.reactButton.classList.remove("active"))})},_markReactionAsActive:function(){for(var e=elData(this._objects.get(this._popoverCurrentObjectId)[0].reactButton,"reaction-type-id"),t=elBySelAll(".reactionTypeButton.active",this._getPopover()),i=0,n=t.length;i<n;i++)t[i].classList.remove("active");0!=e&&elBySel('.reactionTypeButton[data-reaction-type-id="'+e+'"]',this._getPopover()).classList.add("active")},_toggleReactPopover:function(e,t,i){if(null!==i&&(i.preventDefault(),i.stopPropagation()),1===Object.keys(REACTION_TYPES).length){var n=REACTION_TYPES[Object.keys(REACTION_TYPES)[0]];this._popoverCurrentObjectId=e,this._react(n.reactionTypeID)}else 0===this._popoverCurrentObjectId||this._popoverCurrentObjectId!==e?this._openReactPopover(e,t):this._closePopover(e,t)},_openReactPopover:function(e,t){if(0!==this._popoverCurrentObjectId&&this._closePopover(),this._popoverCurrentObjectId=e,this._markReactionAsActive(),h.set(this._getPopover(),t,{pointer:!0,horizontal:this._options.isButtonGroupNavigation?"left":"center",vertical:"top"}),this._options.isButtonGroupNavigation){t.closest("nav").style.opacity="1"}this._getPopover().classList.remove("forceHide"),this._getPopover().classList.add("active")},_getPopover:function(){if(null==this._popover){this._popover=elCreate("div"),this._popover.className="reactionPopover forceHide";var e=elCreate("div");e.className="reactionPopoverContent";var t=elCreate("ul"),i=this._getSortedReactionTypes();for(var n in i)if(i.hasOwnProperty(n)){var o=i[n],r=elCreate("li");r.className="reactionTypeButton jsTooltip",elData(r,"reaction-type-id",o.reactionTypeID),elData(r,"title",o.title),r.title=o.title;var s=elCreate("span");s.classList="reactionTypeButtonTitle",s.innerHTML=o.title,r.innerHTML=o.renderedIcon,r.appendChild(s),r.addEventListener(WCF_CLICK_EVENT,this._react.bind(this,o.reactionTypeID)),t.appendChild(r)}e.appendChild(t),this._popover.appendChild(e);var l=elCreate("span");l.className="elementPointer",l.appendChild(elCreate("span")),this._popover.appendChild(l),document.body.appendChild(this._popover),a.trigger()}return this._popover},_getSortedReactionTypes:function(){var e=[];for(var t in REACTION_TYPES)REACTION_TYPES.hasOwnProperty(t)&&e.push(REACTION_TYPES[t]);return e.sort(function(e,t){return e.showOrder-t.showOrder}),e},_closePopover:function(){0!==this._popoverCurrentObjectId&&(this._getPopover().classList.remove("active"),this._options.isButtonGroupNavigation&&this._objects.get(this._popoverCurrentObjectId).forEach(function(e){e.reactButton.closest("nav").style.cssText=""}),this._popoverCurrentObjectId=0)},_react:function(t){this._options.parameters.reactionTypeID=t,this._options.parameters.data.containerID=this._currentReactionTypeId,this._options.parameters.data.objectID=this._popoverCurrentObjectId,this._options.parameters.data.objectType=this._objectType,e.api(this,{parameters:this._options.parameters}),this._closePopover()},_ajaxSuccess:function(e){this.countButtons.updateCountButtons(e.returnValues.objectID,e.returnValues.reactions),this._updateReactButton(e.returnValues.objectID,e.returnValues.reactionTypeID)},_ajaxSetup:function(){return{data:{actionName:"react",className:"\\wcf\\data\\reaction\\ReactionAction"}}}},g}),define("WoltLabSuite/Core/Ui/Like/Handler",["Ajax","Core","Dictionary","Language","ObjectMap","StringUtil","Dom/ChangeListener","Dom/Util","Ui/Dialog","WoltLabSuite/Core/Ui/User/List","User","WoltLabSuite/Core/Ui/Reaction/Handler"],function(e,t,i,n,o,r,a,s,l,c,u,d){"use strict";function h(e,t){this.init(e,t)}return h.prototype={init:function(e,i){if(""===i.containerSelector)throw new Error("[WoltLabSuite/Core/Ui/Like/Handler] Expected a non-empty string for option 'containerSelector'.");this._containers=new o,this._details=new o,this._objectType=e,this._options=t.extend({badgeClassNames:"",isSingleItem:!1,markListItemAsActive:!1,renderAsButton:!0,summaryPrepend:!0,summaryUseIcon:!0,canDislike:!1,canLike:!1,canLikeOwnContent:!1,canViewSummary:!1,badgeContainerSelector:".messageHeader .messageStatus",buttonAppendToSelector:".messageFooter .messageFooterButtons",buttonBeforeSelector:"",containerSelector:"",summarySelector:".messageFooterGroup"},i),this.initContainers(i,e),a.add("WoltLabSuite/Core/Ui/Like/Handler-"+e,this.initContainers.bind(this)),new d(this._objectType,{containerSelector:this._options.containerSelector,summaryListSelector:".reactionSummaryList"})},initContainers:function(){for(var e,t,i=elBySelAll(this._options.containerSelector),n=!1,o=0,r=i.length;o<r;o++)e=i[o],this._containers.has(e)||(t={badge:null,dislikeButton:null,likeButton:null,summary:null,dislikes:~~elData(e,"like-dislikes"),liked:~~elData(e,"like-liked"),likes:~~elData(e,"like-likes"),objectId:~~elData(e,"object-id"),users:JSON.parse(elData(e,"like-users"))},this._containers.set(e,t),this._buildWidget(e,t),n=!0);n&&a.trigger()},_buildWidget:function(e,t){var i,n,o,a=!0;if(o=this._options.isSingleItem?elBySel(this._options.summarySelector):elBySel(this._options.summarySelector,e),
+null===o&&(o=this._options.isSingleItem?elBySel(this._options.badgeContainerSelector):elBySel(this._options.badgeContainerSelector,e),a=!1),null!==o){i=elCreate("ul"),i.classList.add("reactionSummaryList"),a?i.classList.add("likesSummary"):i.classList.add("reactionSummaryListTiny");for(var l in t.users)if("reactionTypeID"!==l&&REACTION_TYPES.hasOwnProperty(l)){var c=elCreate("li");c.className="reactCountButton",elData(c,"reaction-type-id",l);var d=elCreate("span");d.className="reactionCount",d.innerHTML=r.shortUnit(t.users[l]),c.appendChild(d),c.innerHTML=c.innerHTML+REACTION_TYPES[l].renderedIcon,i.appendChild(c)}a?this._options.summaryPrepend?s.prepend(i,o):o.appendChild(i):"OL"===o.nodeName||"UL"===o.nodeName?(n=elCreate("li"),n.appendChild(i),o.appendChild(n)):o.appendChild(i),t.badge=i}if(this._options.canLike&&(u.userId!=elData(e,"user-id")||this._options.canLikeOwnContent)){var h=this._options.buttonAppendToSelector?this._options.isSingleItem?elBySel(this._options.buttonAppendToSelector):elBySel(this._options.buttonAppendToSelector,e):null,f=this._options.buttonBeforeSelector?this._options.isSingleItem?elBySel(this._options.buttonBeforeSelector):elBySel(this._options.buttonBeforeSelector,e):null;if(null===f&&null===h)throw new Error("Unable to find insert location for like/dislike buttons.");t.likeButton=this._createButton(e,t.users.reactionTypeID,f,h)}},_createButton:function(e,t,i,o){var r=n.get("wcf.reactions.react"),a=elCreate("li");if(a.className="wcfReactButton",i)var s=i.parentElement.contains("jsMobileNavigation");else var s=o.classList.contains("jsMobileNavigation");var l=elCreate("a");l.className="jsTooltip reactButton",this._options.renderAsButton&&(l.classList.add("button"),s&&l.classList.add("ignoreMobileNavigation")),l.href="#",l.title=r;var c=elCreate("span");c.className="icon icon16 fa-smile-o",void 0===t||0==t?elData(c,"reaction-type-id",0):(elData(l,"reaction-type-id",t),l.classList.add("active")),l.appendChild(c);var u=elCreate("span");return u.className="invisible",u.innerHTML=r,l.appendChild(document.createTextNode(" ")),l.appendChild(u),a.appendChild(l),i?i.parentNode.insertBefore(a,i):o.appendChild(a),l}},h}),define("WoltLabSuite/Core/Ui/Message/InlineEditor",["Ajax","Core","Dictionary","Environment","EventHandler","Language","ObjectMap","Dom/ChangeListener","Dom/Traverse","Dom/Util","Ui/Notification","Ui/ReusableDropdown","WoltLabSuite/Core/Ui/Scroll"],function(e,t,i,n,o,r,a,s,l,c,u,d,h){"use strict";var f=function(){};return f.prototype={init:function(){},rebuild:function(){},_click:function(){},_clickDropdown:function(){},_dropdownBuild:function(){},_dropdownToggle:function(){},_dropdownGetItems:function(){},_dropdownOpen:function(){},_dropdownSelect:function(){},_clickDropdownItem:function(){},_prepare:function(){},_showEditor:function(){},_restoreMessage:function(){},_save:function(){},_validate:function(){},throwError:function(){},_showMessage:function(){},_hideEditor:function(){},_restoreEditor:function(){},_destroyEditor:function(){},_getHash:function(){},_updateHistory:function(){},_getEditorId:function(){},_getObjectId:function(){},_ajaxFailure:function(){},_ajaxSuccess:function(){},_ajaxSetup:function(){},legacyEdit:function(){}},f}),define("WoltLabSuite/Core/Ui/Message/Manager",["Ajax","Core","Dictionary","Language","Dom/ChangeListener","Dom/Util"],function(e,t,i,n,o,r){"use strict";var a=function(){};return a.prototype={init:function(){},rebuild:function(){},getPermission:function(){},getPropertyValue:function(){},update:function(){},updateItems:function(){},updateAllItems:function(){},setNote:function(){},_update:function(){},_updateState:function(){},_toggleMessageStatus:function(){},_getAttributeName:function(){},_ajaxSuccess:function(){},_ajaxSetup:function(){}},a}),define("WoltLabSuite/Core/Ui/Message/Reply",["Ajax","Core","EventHandler","Language","Dom/ChangeListener","Dom/Util","Dom/Traverse","Ui/Dialog","Ui/Notification","WoltLabSuite/Core/Ui/Scroll","EventKey","User","WoltLabSuite/Core/Controller/Captcha"],function(e,t,i,n,o,r,a,s,l,c,u,d,h){"use strict";var f=function(){};return f.prototype={init:function(){},_submitGuestDialog:function(){},_submit:function(){},_validate:function(){},throwError:function(){},_showLoadingOverlay:function(){},_hideLoadingOverlay:function(){},_reset:function(){},_handleError:function(){},_getEditor:function(){},_insertMessage:function(){},_ajaxSuccess:function(){},_ajaxFailure:function(){},_ajaxSetup:function(){}},f}),define("WoltLabSuite/Core/Ui/Message/Share",["EventHandler","StringUtil"],function(e,t){"use strict";return{_pageDescription:"",_pageUrl:"",init:function(){var i=elBySel('meta[property="og:title"]');null!==i&&(this._pageDescription=encodeURIComponent(i.content));var n=elBySel('meta[property="og:url"]');null!==n&&(this._pageUrl=encodeURIComponent(n.content)),elBySelAll(".jsMessageShareButtons",null,function(i){i.classList.remove("jsMessageShareButtons");var n=encodeURIComponent(t.unescapeHTML(elData(i,"url")||""));n||(n=this._pageUrl);var o={facebook:{link:elBySel(".jsShareFacebook",i),share:function(e){this._share("facebook","https://www.facebook.com/sharer.php?u={pageURL}&t={text}",!0,n)}.bind(this)},google:{link:elBySel(".jsShareGoogle",i),share:function(e){this._share("google","https://plus.google.com/share?url={pageURL}",!1,n)}.bind(this)},reddit:{link:elBySel(".jsShareReddit",i),share:function(e){this._share("reddit","https://ssl.reddit.com/submit?url={pageURL}",!1,n)}.bind(this)},twitter:{link:elBySel(".jsShareTwitter",i),share:function(e){this._share("twitter","https://twitter.com/share?url={pageURL}&text={text}",!1,n)}.bind(this)},linkedIn:{link:elBySel(".jsShareLinkedIn",i),share:function(e){this._share("linkedIn","https://www.linkedin.com/cws/share?url={pageURL}",!1,n)}.bind(this)},pinterest:{link:elBySel(".jsSharePinterest",i),share:function(e){this._share("pinterest","https://www.pinterest.com/pin/create/link/?url={pageURL}&description={text}",!1,n)}.bind(this)},xing:{link:elBySel(".jsShareXing",i),share:function(e){this._share("xing","https://www.xing.com/social_plugins/share?url={pageURL}",!1,n)}.bind(this)},whatsApp:{link:elBySel(".jsShareWhatsApp",i),share:function(){window.location.href="whatsapp://send?text="+this._pageDescription+"%20"+n}.bind(this)}};e.fire("com.woltlab.wcf.message.share","shareProvider",{container:i,providers:o,pageDescription:this._pageDescription,pageUrl:this._pageUrl});for(var r in o)o.hasOwnProperty(r)&&null!==o[r].link&&o[r].link.addEventListener(WCF_CLICK_EVENT,o[r].share)}.bind(this))},_share:function(e,t,i,n){n||(n=this._pageUrl),window.open(t.replace(/\{pageURL}/,n).replace(/\{text}/,this._pageDescription+(i?"%20"+n:"")),e,"height=600,width=600")}}}),define("WoltLabSuite/Core/Ui/Page/Search",["Ajax","EventKey","Language","StringUtil","Dom/Util","Ui/Dialog"],function(e,t,i,n,o,r){"use strict";var a=function(){};return a.prototype={open:function(){},_search:function(){},_click:function(){},_ajaxSuccess:function(){},_ajaxSetup:function(){},_dialogSetup:function(){}},a}),define("WoltLabSuite/Core/Ui/Sortable/List",["Core","Ui/Screen"],function(e,t){"use strict";var i=function(){};return i.prototype={init:function(){},_enable:function(){},_disable:function(){}},i}),define("WoltLabSuite/Core/Ui/Poll/Editor",["Core","Dom/Util","EventHandler","EventKey","Language","WoltLabSuite/Core/Date/Picker","WoltLabSuite/Core/Ui/Sortable/List"],function(e,t,i,n,o,r,a){"use strict";function s(e,t,i,n){this.init(e,t,i,n)}return s.prototype={init:function(t,n,o,r){if(this._container=elById(t),null===this._container)throw new Error("Unknown poll editor container with id '"+t+"'.");if(this._wysiwygId=o,""!==o&&null===elById(o))throw new Error("Unknown wysiwyg field with id '"+o+"'.");this.questionField=elById(this._wysiwygId+"Poll_question");var s=elByClass("sortableList",this._container);if(0===s.length)throw new Error("Cannot find poll options list for container with id '"+t+"'.");if(this.optionList=s[0],this.endTimeField=elById(this._wysiwygId+"Poll_endTime"),this.maxVotesField=elById(this._wysiwygId+"Poll_maxVotes"),this.isChangeableYesField=elById(this._wysiwygId+"Poll_isChangeable"),this.isChangeableNoField=elById(this._wysiwygId+"Poll_isChangeable_no"),this.isPublicYesField=elById(this._wysiwygId+"Poll_isPublic"),this.isPublicNoField=elById(this._wysiwygId+"Poll_isPublic_no"),this.resultsRequireVoteYesField=elById(this._wysiwygId+"Poll_resultsRequireVote"),this.resultsRequireVoteNoField=elById(this._wysiwygId+"Poll_resultsRequireVote_no"),this.sortByVotesYesField=elById(this._wysiwygId+"Poll_sortByVotes"),this.sortByVotesNoField=elById(this._wysiwygId+"Poll_sortByVotes_no"),this._optionCount=0,this._options=e.extend({isAjax:!1,maxOptions:20},r),this._createOptionList(n||[]),new a({containerId:t,options:{toleranceElement:"> div"}}),this._options.isAjax)for(var l=["handleError","reset","submit","validate"],c=0,u=l.length;c<u;c++){var d=l[c];i.add("com.woltlab.wcf.redactor2",d+"_"+this._wysiwygId,this["_"+d].bind(this))}else{var h=this._container.closest("form");if(null===h)throw new Error("Cannot find form for container with id '"+t+"'.");h.addEventListener("submit",this._submit.bind(this))}},_addOption:function(e){if(e.preventDefault(),this._optionCount===this._options.maxOptions)return!1;this._createOption(void 0,void 0,e.currentTarget.closest("li"))},_createOption:function(e,i,n){e=e||"",i=~~i||0;var r=elCreate("LI");r.className="sortableNode",elData(r,"option-id",i),n?t.insertAfter(r,n):this.optionList.appendChild(r);var a=elCreate("div");a.className="pollOptionInput",r.appendChild(a);var s=elCreate("span");s.className="icon icon16 fa-arrows sortableNodeHandle",a.appendChild(s);var l=elCreate("a");elAttr(l,"role","button"),elAttr(l,"href","#"),l.className="icon icon16 fa-plus jsTooltip jsAddOption pointer",elAttr(l,"title",o.get("wcf.poll.button.addOption")),l.addEventListener("click",this._addOption.bind(this)),a.appendChild(l);var c=elCreate("a");elAttr(c,"role","button"),elAttr(c,"href","#"),c.className="icon icon16 fa-times jsTooltip jsDeleteOption pointer",elAttr(c,"title",o.get("wcf.poll.button.removeOption")),c.addEventListener("click",this._removeOption.bind(this)),a.appendChild(c);var u=elCreate("input");elAttr(u,"type","text"),u.value=e,elAttr(u,"maxlength",255),u.addEventListener("keydown",this._optionInputKeyDown.bind(this)),u.addEventListener("click",function(){document.activeElement!==this&&this.focus()}),a.appendChild(u),null!==n&&u.focus(),++this._optionCount===this._options.maxOptions&&elBySelAll("span.jsAddOption",this.optionList,function(e){e.classList.remove("pointer"),e.classList.add("disabled")})},_createOptionList:function(e){for(var t=0,i=e.length;t<i;t++){var n=e[t];this._createOption(n.optionValue,n.optionID)}this._optionCount<this._options.maxOptions&&this._createOption()},_handleError:function(e){switch(e.returnValues.fieldName){case this._wysiwygId+"Poll_endTime":case this._wysiwygId+"Poll_maxVotes":var i=e.returnValues.fieldName.replace(this._wysiwygId+"Poll_",""),n=elCreate("small");n.className="innerError",n.innerHTML=o.get("wcf.poll."+i+".error."+e.returnValues.errorType);var r=elById(e.returnValues.fieldName);r.closest("dd");t.prepend(n,r.nextSibling),e.cancel=!0}},_optionInputKeyDown:function(t){n.Enter(t)&&(e.triggerEvent(elByClass("jsAddOption",t.currentTarget.parentNode)[0],"click"),t.preventDefault())},_removeOption:function(e){e.preventDefault(),elRemove(e.currentTarget.closest("li")),this._optionCount--,elBySelAll("span.jsAddOption",this.optionList,function(e){e.classList.add("pointer"),e.classList.remove("disabled")}),0===this.optionList.length&&this._createOption()},_reset:function(){this.questionField.value="",this._optionCount=0,this.optionList.innerHtml="",this._createOption(),r.clear(this.endTimeField),this.maxVotesField.value=1,this.isChangeableYesField.checked=!1,this.isChangeableNoField.checked=!0,this.isPublicYesField=!1,this.isPublicNoField=!0,this.resultsRequireVoteYesField=!1,this.resultsRequireVoteNoField=!0,this.sortByVotesYesField=!1,this.sortByVotesNoField=!0,i.fire("com.woltlab.wcf.poll.editor","reset",{pollEditor:this})},_submit:function(e){for(var t=[],n=0,o=this.optionList.children.length;n<o;n++){var r=this.optionList.children[n],a=elBySel("input[type=text]",r).value.trim();""!==a&&t.push(elData(r,"option-id")+"_"+a)}if(this._options.isAjax)e.poll={},e.poll[this.questionField.id]=this.questionField.value,e.poll[this._wysiwygId+"Poll_options"]=t,e.poll[this.endTimeField.id]=this.endTimeField.value,e.poll[this.maxVotesField.id]=this.maxVotesField.value,e.poll[this.isChangeableYesField.id]=!!this.isChangeableYesField.checked,e.poll[this.isPublicYesField.id]=!!this.isPublicYesField.checked,e.poll[this.resultsRequireVoteYesField.id]=!!this.resultsRequireVoteYesField.checked,e.poll[this.sortByVotesYesField.id]=!!this.sortByVotesYesField.checked,i.fire("com.woltlab.wcf.poll.editor","submit",{event:e,pollEditor:this});else for(var s=this._container.closest("form"),n=0,o=t.length;n<o;n++){var l=elCreate("input");elAttr(l,"type","hidden"),elAttr(l,"name",this._wysiwygId+"Poll_options["+n+"]"),l.value=t[n],s.appendChild(l)}},_validate:function(e){if(""!==this.questionField.value.trim()){for(var t=0,n=0,r=this.optionList.children.length;n<r;n++){""!==elBySel("input[type=text]",this.optionList.children[n]).value.trim()&&t++}if(0===t)e.api.throwError(this._container,o.get("wcf.global.form.error.empty")),e.valid=!1;else{var a=~~this.maxVotesField.value;a&&a>t?(e.api.throwError(this.maxVotesField.parentNode,o.get("wcf.poll.maxVotes.error.invalid")),e.valid=!1):i.fire("com.woltlab.wcf.poll.editor","validate",{data:e,pollEditor:this})}}}},s}),define("WoltLabSuite/Core/Ui/Redactor/Article",["WoltLabSuite/Core/Ui/Article/Search"],function(e){"use strict";var t=function(){};return t.prototype={init:function(){},_click:function(){},_insert:function(){}},t}),define("WoltLabSuite/Core/Ui/Redactor/Metacode",["EventHandler","Dom/Util"],function(e,t){"use strict";var i=function(){};return i.prototype={convert:function(){},convertFromHtml:function(){},_getOpeningTag:function(){},_getClosingTag:function(){},_getFirstParagraph:function(){},_getLastParagraph:function(){},_parseAttributes:function(){}},i}),define("WoltLabSuite/Core/Ui/Redactor/Autosave",["Core","Devtools","EventHandler","Language","Dom/Traverse","./Metacode"],function(e,t,i,n,o,r){"use strict";var a=function(){};return a.prototype={init:function(){},getInitialValue:function(){},getMetaData:function(){},watch:function(){},destroy:function(){},clear:function(){},createOverlay:function(){},hideOverlay:function(){},_saveToStorage:function(){},_cleanup:function(){}},a}),define("WoltLabSuite/Core/Ui/Redactor/PseudoHeader",[],function(){"use strict";var e=function(){};return e.prototype={getHeight:function(){}},e}),define("WoltLabSuite/Core/Ui/Redactor/Code",["EventHandler","EventKey","Language","StringUtil","Dom/Util","Ui/Dialog","./PseudoHeader","prism/prism-meta"],function(e,t,i,n,o,r,a,s){"use strict";var l=function(){};return l.prototype={init:function(){},_bbcodeCode:function(){},_observeLoad:function(){},_edit:function(){},_setTitle:function(){},_delete:function(){},_dialogSetup:function(){},_dialogSubmit:function(){}},l}),define("WoltLabSuite/Core/Ui/Redactor/Format",["Dom/Util"],function(e){"use strict";var t=function(){};return t.prototype={format:function(){},removeFormat:function(){},_handleParentNodes:function(){},_getLastMatchingParent:function(){},_isBoundaryElement:function(){},_getSelectionMarker:function(){}},t}),define("WoltLabSuite/Core/Ui/Redactor/Html",["EventHandler","EventKey","Language","StringUtil","Dom/Util","Ui/Dialog","./PseudoHeader"],function(e,t,i,n,o,r,a){"use strict";var s=function(){};return s.prototype={init:function(){},_bbcodeCode:function(){},_observeLoad:function(){},_edit:function(){},_save:function(){},_setTitle:function(){},_delete:function(){},_dialogSetup:function(){}},s}),define("WoltLabSuite/Core/Ui/Redactor/Link",["Core","EventKey","Language","Ui/Dialog"],function(e,t,i,n){"use strict";var o=function(){};return o.prototype={showDialog:function(){},_submit:function(){},_dialogSetup:function(){}},o}),define("WoltLabSuite/Core/Ui/Redactor/Mention",["Ajax","Environment","StringUtil","Ui/CloseOverlay"],function(e,t,i,n){"use strict";var o=function(){};return o.prototype={init:function(){},_keyDown:function(){},_keyUp:function(){},_getTextLineInFrontOfCaret:function(){},_getDropdownMenuPosition:function(){},_setUsername:function(){},_selectMention:function(){},_updateDropdownPosition:function(){},_selectItem:function(){},_hideDropdown:function(){},_ajaxSetup:function(){},_ajaxSuccess:function(){}},o}),define("WoltLabSuite/Core/Ui/Redactor/Page",["WoltLabSuite/Core/Ui/Page/Search"],function(e){"use strict";var t=function(){};return t.prototype={init:function(){},_click:function(){},_insert:function(){}},t}),define("WoltLabSuite/Core/Ui/Redactor/Quote",["Core","EventHandler","EventKey","Language","StringUtil","Dom/Util","Ui/Dialog","./Metacode","./PseudoHeader"],function(e,t,i,n,o,r,a,s,l){"use strict";var c=function(){};return c.prototype={init:function(){},_insertQuote:function(){},_click:function(){},_observeLoad:function(){},_edit:function(){},_save:function(){},_setTitle:function(){},_delete:function(){},_dialogSetup:function(){},_dialogSubmit:function(){}},c}),define("WoltLabSuite/Core/Ui/Redactor/Spoiler",["EventHandler","EventKey","Language","StringUtil","Dom/Util","Ui/Dialog","./PseudoHeader"],function(e,t,i,n,o,r,a){"use strict";var s=function(){};return s.prototype={init:function(){},_bbcodeSpoiler:function(){},_observeLoad:function(){},_edit:function(){},_setTitle:function(){},_delete:function(){},_dialogSetup:function(){},_dialogSubmit:function(){}},s}),define("WoltLabSuite/Core/Ui/Redactor/Table",["Language","Ui/Dialog"],function(e,t){"use strict";var i=function(){};return i.prototype={showDialog:function(){},_submit:function(){},_dialogSetup:function(){}},i}),define("WoltLabSuite/Core/Ui/Search/Page",["Core","Dom/Traverse","Dom/Util","Ui/Screen","Ui/SimpleDropdown","./Input"],function(e,t,i,n,o,r){"use strict";return{init:function(a){var s=elById("pageHeaderSearchInput");new r(s,{ajax:{className:"wcf\\data\\search\\keyword\\SearchKeywordAction"},callbackDropdownInit:function(e){if(e.classList.add("dropdownMenuPageSearch"),n.is("screen-lg")){elData(e,"dropdown-alignment-horizontal","right");var t=s.clientWidth;e.style.setProperty("min-width",t+"px","");var o=s.parentNode,r=i.offset(o).left+o.clientWidth-(i.offset(s).left+t),a=i.styleAsInt(window.getComputedStyle(o),"padding-bottom");e.style.setProperty("transform","translateX(-"+Math.ceil(r)+"px) translateY(-"+a+"px)","")}},callbackSelect:function(){return setTimeout(function(){t.parentByTag(s,"FORM").submit()},1),!0}});var l=o.getDropdownMenu(i.identify(elBySel(".pageHeaderSearchType"))),c=this._click.bind(this);elBySelAll("a[data-object-type]",l,function(e){e.addEventListener(WCF_CLICK_EVENT,c)});var u=elBySel('a[data-object-type="'+a+'"]',l);e.triggerEvent(u,WCF_CLICK_EVENT)},_click:function(e){e.preventDefault();var t=elById("pageHeader");t.classList.add("searchBarForceOpen"),window.setTimeout(function(){t.classList.remove("searchBarForceOpen")},10);var i=elData(e.currentTarget,"object-type"),n=elById("pageHeaderSearchParameters");n.innerHTML="";var o=elData(e.currentTarget,"extended-link");o&&(elBySel(".pageHeaderSearchExtendedLink").href=o);var r=elData(e.currentTarget,"parameters");r=r?JSON.parse(r):{},i&&(r["types[]"]=i);for(var a in r)if(r.hasOwnProperty(a)){var s=elCreate("input");s.type="hidden",s.name=a,s.value=r[a],n.appendChild(s)}elBySel(".pageHeaderSearchType > .button > .pageHeaderSearchTypeLabel",elById("pageHeaderSearchInputContainer")).textContent=e.currentTarget.textContent}}}),define("WoltLabSuite/Core/Ui/Smiley/Insert",["EventHandler","EventKey"],function(e,t){"use strict";function i(e){this.init(e)}return i.prototype={_editorId:"",init:function(e){this._editorId=e;var t=elById("smilies-"+this._editorId);if(!t&&!(t=elById(this._editorId+"SmiliesTabContainer")))throw new Error("Unable to find the message tab menu container containing the smilies.");t.addEventListener("keydown",this._keydown.bind(this)),t.addEventListener("mousedown",this._mousedown.bind(this))},_keydown:function(e){var i=document.activeElement;if(i.classList.contains("jsSmiley"))if(t.ArrowLeft(e)||t.ArrowRight(e)||t.Home(e)||t.End(e)){e.preventDefault();var n=Array.prototype.slice.call(elBySelAll(".jsSmiley",e.currentTarget));t.ArrowLeft(e)&&n.reverse();var o=n.indexOf(i);t.Home(e)?o=0:t.End(e)?o=n.length-1:(o+=1)===n.length&&(o=0),n[o].focus()}else(t.Enter(e)||t.Space(e))&&(e.preventDefault(),this._insert(elBySel("img",i)))},_mousedown:function(e){e.preventDefault();var t=e.target.closest("li"),i=elBySel("img",t);i&&this._insert(i)},_insert:function(t){e.fire("com.woltlab.wcf.redactor2","insertSmiley_"+this._editorId,{img:t})}},i}),define("WoltLabSuite/Core/Ui/Style/FontAwesome",["Language","Ui/Dialog","WoltLabSuite/Core/Ui/ItemList/Filter"],function(e,t,i){"use strict";var n=function(){};return n.prototype={setup:function(){},open:function(){},_click:function(){},_dialogSetup:function(){}},n}),define("WoltLabSuite/Core/Ui/Toggle/Input",["Core"],function(e){"use strict";function t(e,t){this.init(e,t)}return t.prototype={init:function(t,i){if(this._element=elBySel(t),null===this._element)throw new Error("Unable to find element by selector '"+t+"'.");var n="INPUT"===this._element.nodeName?elAttr(this._element,"type"):"";if("checkbox"!==n&&"radio"!==n)throw new Error("Illegal element, expected input[type='checkbox'] or input[type='radio'].");this._options=e.extend({hide:[],show:[]},i),["hide","show"].forEach(function(e){var t,i,n;for(i=0,n=this._options[e].length;i<n;i++)if("string"!=typeof(t=this._options[e][i])&&!(t instanceof Element))throw new TypeError("The array '"+e+"' may only contain string selectors or DOM elements.")}.bind(this)),this._element.addEventListener("change",this._change.bind(this)),this._handleElements(this._options.show,this._element.checked),this._handleElements(this._options.hide,!this._element.checked)},_change:function(e){var t=e.currentTarget.checked;this._handleElements(this._options.show,t),this._handleElements(this._options.hide,!t)},_handleElements:function(e,t){for(var i,n,o=0,r=e.length;o<r;o++){if("string"==typeof(i=e[o])){if(null===(n=elBySel(i)))throw new Error("Unable to find element by selector '"+i+"'.");e[o]=i=n}window[t?"elShow":"elHide"](i)}}},t}),define("WoltLabSuite/Core/Ui/User/Editor",["Ajax","Language","StringUtil","Dom/Util","Ui/Dialog","Ui/Notification"],function(e,t,i,n,o,r){"use strict";var a=function(){};return a.prototype={init:function(){},_click:function(){},_submit:function(){},_ajaxSuccess:function(){},_ajaxSetup:function(){},_dialogSetup:function(){}},a}),define("WoltLabSuite/Core/Controller/Condition/Page/Dependence",["Dom/ChangeListener","Dom/Traverse","EventHandler","ObjectMap"],function(e,t,i,n){"use strict";var o=function(){};return o.prototype={register:function(){},_checkVisibility:function(){},_hideDependentElement:function(){},_showDependentElement:function(){}},o}),define("WoltLabSuite/Core/Controller/Map/Route/Planner",["Dom/Traverse","Dom/Util","Language","Ui/Dialog","WoltLabSuite/Core/Ajax/Status"],function(e,t,i,n,o){function r(e,t){if(this._button=elById(e),null===this._button)throw new Error("Unknown button with id '"+e+"'");this._button.addEventListener("click",this._openDialog.bind(this)),this._destination=t}return r.prototype={_dialogSetup:function(){return{id:this._button.id+"Dialog",options:{onShow:this._initDialog.bind(this),title:i.get("wcf.map.route.planner")},source:'<div class="googleMapsDirectionsContainer" style="display: none;"><div class="googleMap"></div><div class="googleMapsDirections"></div></div><small class="googleMapsDirectionsGoogleLinkContainer"><a href="'+this._getGoogleMapsLink()+'" class="googleMapsDirectionsGoogleLink" target="_blank" style="display: none;">'+i.get("wcf.map.route.viewOnGoogleMaps")+"</a></small><dl><dt>"+i.get("wcf.map.route.origin")+'</dt><dd><input type="text" name="origin" class="long" autofocus /></dd></dl><dl style="display: none;"><dt>'+i.get("wcf.map.route.travelMode")+'</dt><dd><select name="travelMode"><option value="driving">'+i.get("wcf.map.route.travelMode.driving")+'</option><option value="walking">'+i.get("wcf.map.route.travelMode.walking")+'</option><option value="bicycling">'+i.get("wcf.map.route.travelMode.bicycling")+'</option><option value="transit">'+i.get("wcf.map.route.travelMode.transit")+"</option></select></dd></dl>"}},_calculateRoute:function(e){var t=n.getDialog(this).dialog;e.label&&(this._originInput.value=e.label),void 0===this._map&&(this._map=new google.maps.Map(elByClass("googleMap",t)[0],{disableDoubleClickZoom:WCF.Location.GoogleMaps.Settings.get("disableDoubleClickZoom"),draggable:WCF.Location.GoogleMaps.Settings.get("draggable"),mapTypeId:google.maps.MapTypeId.ROADMAP,scaleControl:WCF.Location.GoogleMaps.Settings.get("scaleControl"),scrollwheel:WCF.Location.GoogleMaps.Settings.get("scrollwheel")}),this._directionsService=new google.maps.DirectionsService,this._directionsRenderer=new google.maps.DirectionsRenderer,this._directionsRenderer.setMap(this._map),this._directionsRenderer.setPanel(elByClass("googleMapsDirections",t)[0]),this._googleLink=elByClass("googleMapsDirectionsGoogleLink",t)[0]);var i={destination:this._destination,origin:e.location,provideRouteAlternatives:!0,travelMode:google.maps.TravelMode[this._travelMode.value.toUpperCase()]};o.show(),this._directionsService.route(i,this._setRoute.bind(this)),elAttr(this._googleLink,"href",this._getGoogleMapsLink(e.location,this._travelMode.value)),this._lastOrigin=e.location},_getGoogleMapsLink:function(e,t){if(e){var i="https://www.google.com/maps/dir/?api=1&origin="+e.lat()+","+e.lng()+"&destination="+this._destination.lat()+","+this._destination.lng();return t&&(i+="&travelmode="+t),i}return"https://www.google.com/maps/search/?api=1&query="+this._destination.lat()+","+this._destination.lng()},_initDialog:function(){if(!this._didInitDialog){var e=n.getDialog(this).dialog;this._originInput=elBySel('input[name="origin"]',e),new WCF.Location.GoogleMaps.LocationSearch(this._originInput,this._calculateRoute.bind(this)),this._travelMode=elBySel('select[name="travelMode"]',e),this._travelMode.addEventListener("change",this._updateRoute.bind(this)),this._didInitDialog=!0}},_openDialog:function(){n.open(this)},_setRoute:function(t,n){o.hide(),"OK"===n?(elShow(this._map.getDiv().parentNode),google.maps.event.trigger(this._map,"resize"),this._directionsRenderer.setDirections(t),elShow(e.parentByTag(this._travelMode,"DL")),elShow(this._googleLink),elInnerError(this._originInput,!1)):("OVER_QUERY_LIMIT"!==n&&"REQUEST_DENIED"!==n&&(n="NOT_FOUND"),elInnerError(this._originInput,i.get("wcf.map.route.error."+n.toLowerCase())))},_updateRoute:function(){this._calculateRoute({location:this._lastOrigin})}},r}),define("WoltLabSuite/Core/Controller/User/Notification/Settings",["Dictionary","Language","Dom/Traverse","Ui/SimpleDropdown"],function(e,t,i,n){"use strict";var o=function(){};return o.prototype={setup:function(){},_initGroup:function(){},_click:function(){},_createDropdown:function(){},_selectType:function(){}},o}),define("WoltLabSuite/Core/Form/Builder/Field/Captcha",["Core","./Field","WoltLabSuite/Core/Controller/Captcha"],function(e,t,i){"use strict";function n(e){this.init(e)}return e.inherit(n,t,{_getData:function(){return i.has(this._fieldId)?i.getData(this._fieldId):{}},_readField:function(){},destroy:function(){i.has(this._fieldId)&&i.delete(this._fieldId)}}),n}),define("WoltLabSuite/Core/Form/Builder/Field/Checkboxes",["Core","./Field"],function(e,t){"use strict";function i(e){this.init(e)}return e.inherit(i,t,{_getData:function(){var e={};e[this._fieldId]=[];for(var t=0,i=this._fields.length;t<i;t++)this._fields[t].checked&&e[this._fieldId].push(this._fields[t].value);return e},_readField:function(){this._fields=elBySelAll('input[name="'+this._fieldId+'[]"]')}}),i}),define("WoltLabSuite/Core/Form/Builder/Field/Checked",["Core","./Field"],function(e,t){"use strict";function i(e){this.init(e)}return e.inherit(i,t,{_getData:function(){var e={};return e[this._fieldId]=~~this._field.checked,e}}),i}),define("WoltLabSuite/Core/Form/Builder/Field/Date",["Core","WoltLabSuite/Core/Date/Picker","./Field"],function(e,t,i){"use strict";function n(e){this.init(e)}return e.inherit(n,i,{_getData:function(){var e={};return e[this._fieldId]=t.getValue(this._field),e}}),n}),define("WoltLabSuite/Core/Form/Builder/Field/ItemList",["Core","./Field","WoltLabSuite/Core/Ui/ItemList/Static"],function(e,t,i){"use strict";function n(e){this.init(e)}return e.inherit(n,t,{_getData:function(){var e={};e[this._fieldId]=[];for(var t=i.getValues(this._fieldId),n=0,o=t.length;n<o;n++)t[n].objectId?e[this._fieldId][t[n].objectId]=t[n].value:e[this._fieldId].push(t[n].value);return e}}),n}),define("WoltLabSuite/Core/Form/Builder/Field/RadioButton",["Core","./Field"],function(e,t){"use strict";function i(e){this.init(e)}return e.inherit(i,t,{_getData:function(){for(var e={},t=0,i=this._fields.length;t<i;t++)if(this._fields[t].checked){e[this._fieldId]=this._fields[t].value;break}return e},_readField:function(){this._fields=elBySelAll("input[name="+this._fieldId+"]")}}),i}),define("WoltLabSuite/Core/Form/Builder/Field/SimpleAcl",["Core","./Field"],function(e,t){"use strict";function i(e){this.init(e)}return e.inherit(i,t,{_getData:function(){var e=[];elBySelAll('input[name="'+this._fieldId+'[group][]"]',void 0,function(t){e.push(~~t.value)});var t=[];elBySelAll('input[name="'+this._fieldId+'[user][]"]',void 0,function(e){t.push(~~e.value)});var i={};return i[this._fieldId]={group:e,user:t},i},_readField:function(){}}),i}),define("WoltLabSuite/Core/Form/Builder/Field/Tag",["Core","./Field","WoltLabSuite/Core/Ui/ItemList"],function(e,t,i){"use strict";function n(e){this.init(e)}return e.inherit(n,t,{_getData:function(){var e={};e[this._fieldId]=[];for(var t=i.getValues(this._fieldId),n=0,o=t.length;n<o;n++)e[this._fieldId].push(t[n].value);return e}}),n}),define("WoltLabSuite/Core/Form/Builder/Field/User",["Core","./Field","WoltLabSuite/Core/Ui/ItemList"],function(e,t,i){"use strict";function n(e){this.init(e)}return e.inherit(n,t,{_getData:function(){for(var e=i.getValues(this._fieldId),t=[],n=0,o=e.length;n<o;n++)e[n].objectId&&t.push(e[n].value);var r={};return r[this._fieldId]=t.join(","),r}}),n}),define("WoltLabSuite/Core/Form/Builder/Field/Value",["Core","./Field"],function(e,t){"use strict";function i(e){this.init(e)}return e.inherit(i,t,{_getData:function(){var e={};return e[this._fieldId]=this._field.value,e}}),i}),define("WoltLabSuite/Core/Form/Builder/Field/ValueI18n",["Core","./Field","WoltLabSuite/Core/Language/Input"],function(e,t,i){"use strict";function n(e){this.init(e)}return e.inherit(n,t,{_getData:function(){var e={},t=i.getValues(this._fieldId);return t.size>1?e[this._fieldId+"_i18n"]=t.toObject():e[this._fieldId]=t.get(0),e},destroy:function(){i.unregister(this._fieldId)}}),n}),define("WoltLabSuite/Core/Ui/Comment/Response/Add",["Core","Language","Dom/ChangeListener","Dom/Util","Dom/Traverse","Ui/Notification","WoltLabSuite/Core/Ui/Comment/Add"],function(e,t,i,n,o,r,a){"use strict";var s=function(){};return s.prototype={init:function(){},getContainer:function(){},getContent:function(){},setContent:function(){},_submitGuestDialog:function(){},_submit:function(){},_getParameters:function(){},_validate:function(){},throwError:function(){},_showLoadingOverlay:function(){},_hideLoadingOverlay:function(){},_reset:function(){},_handleError:function(){},_getEditor:function(){},_insertMessage:function(){},_ajaxSuccess:function(){},_ajaxFailure:function(){},_ajaxSetup:function(){}},s}),
+define("WoltLabSuite/Core/Ui/Comment/Response/Edit",["Ajax","Core","Dictionary","Environment","EventHandler","Language","List","Dom/ChangeListener","Dom/Traverse","Dom/Util","Ui/Notification","Ui/ReusableDropdown","WoltLabSuite/Core/Ui/Scroll","WoltLabSuite/Core/Ui/Comment/Edit"],function(e,t,i,n,o,r,a,s,l,c,u,d,h,f){"use strict";var p=function(){};return p.prototype={init:function(){},rebuild:function(){},_click:function(){},_prepare:function(){},_showEditor:function(){},_restoreMessage:function(){},_save:function(){},_validate:function(){},throwError:function(){},_showMessage:function(){},_hideEditor:function(){},_restoreEditor:function(){},_destroyEditor:function(){},_getEditorId:function(){},_getObjectId:function(){},_ajaxFailure:function(){},_ajaxSuccess:function(){},_ajaxSetup:function(){}},p}),define("WoltLabSuite/Core/Ui/Page/Header/Fixed",["Core","EventHandler","Ui/Alignment","Ui/CloseOverlay","Ui/SimpleDropdown","Ui/Screen"],function(e,t,i,n,o,r){"use strict";var a,s,l,c,u,d,h,f=!1;return{init:function(){a=elById("pageHeader"),s=elById("pageHeaderContainer"),this._initSearchBar(),r.on("screen-md-down",{match:function(){f=!0},unmatch:function(){f=!1},setup:function(){f=!0}}),t.add("com.woltlab.wcf.Search","close",this._closeSearchBar.bind(this))},_initSearchBar:function(){c=elById("pageHeaderSearch"),c.addEventListener(WCF_CLICK_EVENT,function(e){e.stopPropagation()}),l=elById("pageHeaderPanel"),u=elById("pageHeaderSearchInput"),d=elById("topMenu"),h=elById("userPanelSearchButton"),h.addEventListener(WCF_CLICK_EVENT,function(e){e.preventDefault(),e.stopPropagation(),a.classList.contains("searchBarOpen")?this._closeSearchBar():this._openSearchBar()}.bind(this)),n.add("WoltLabSuite/Core/Ui/Page/Header/Fixed",function(){a.classList.contains("searchBarForceOpen")||this._closeSearchBar()}.bind(this)),t.add("com.woltlab.wcf.MainMenuMobile","more",function(t){"com.woltlab.wcf.search"===t.identifier&&(t.handler.close(!0),e.triggerEvent(h,WCF_CLICK_EVENT))}.bind(this))},_openSearchBar:function(){window.WCF.Dropdown.Interactive.Handler.closeAll(),a.classList.add("searchBarOpen"),h.parentNode.classList.add("open"),f||i.set(c,d,{horizontal:"right"}),c.style.setProperty("top",l.clientHeight+"px",""),u.focus(),window.setTimeout(function(){u.selectionStart=u.selectionEnd=u.value.length},1)},_closeSearchBar:function(){a.classList.remove("searchBarOpen"),h.parentNode.classList.remove("open"),["bottom","left","right","top"].forEach(function(e){c.style.removeProperty(e)}),u.blur();var e=elBySel(".pageHeaderSearchType",c);o.close(e.id)}}}),define("WoltLabSuite/Core/Ui/Page/Search/Input",["Core","WoltLabSuite/Core/Ui/Search/Input"],function(e,t){"use strict";function i(e,t){this.init(e,t)}return e.inherit(i,t,{init:function(t,n){if(n=e.extend({ajax:{className:"wcf\\data\\page\\PageAction"},callbackSuccess:null},n),"function"!=typeof n.callbackSuccess)throw new Error("Expected a valid callback function for 'callbackSuccess'.");i._super.prototype.init.call(this,t,n),this._pageId=0},setPageId:function(e){this._pageId=e},_getParameters:function(e){var t=i._super.prototype._getParameters.call(this,e);return t.objectIDs=[this._pageId],t},_ajaxSuccess:function(e){this._options.callbackSuccess(e)}}),i}),define("WoltLabSuite/Core/Ui/Page/Search/Handler",["Language","StringUtil","Dom/Util","Ui/Dialog","./Input"],function(e,t,i,n,o){"use strict";var r=null,a=null,s=null,l=null,c=null,u=null;return{open:function(t,i,o,a){r=o,n.open(this),n.setTitle(this,i),s.textContent=a?e.get(a):e.get("wcf.page.pageObjectID.search.terms"),this._getSearchInputHandler().setPageId(t)},_buildList:function(i){if(this._resetList(),!Array.isArray(i.returnValues)||0===i.returnValues.length)return void elInnerError(a,e.get("wcf.page.pageObjectID.search.noResults"));for(var n,o,r,s=0,l=i.returnValues.length;s<l;s++)o=i.returnValues[s],n=o.image,/^fa-/.test(n)&&(n='<span class="icon icon48 '+n+' pointer jsTooltip" title="'+e.get("wcf.global.select")+'"></span>'),r=elCreate("li"),elData(r,"object-id",o.objectID),r.innerHTML='<div class="box48">'+n+'<div><div class="containerHeadline"><h3><a href="'+t.escapeHTML(o.link)+'">'+t.escapeHTML(o.title)+"</a></h3>"+(o.description?"<p>"+o.description+"</p>":"")+"</div></div></div>",r.addEventListener(WCF_CLICK_EVENT,this._click.bind(this)),c.appendChild(r);elShow(u)},_resetList:function(){elInnerError(a,!1),c.innerHTML="",elHide(u)},_getSearchInputHandler:function(){if(null===l){var e=this._buildList.bind(this);l=new o(elById("wcfUiPageSearchInput"),{callbackSuccess:e})}return l},_click:function(e){"A"!==e.target.nodeName&&(e.stopPropagation(),r(elData(e.currentTarget,"object-id")),n.close(this))},_dialogSetup:function(){return{id:"wcfUiPageSearchHandler",options:{onShow:function(){null===a&&(a=elById("wcfUiPageSearchInput"),s=a.parentNode.previousSibling.childNodes[0],c=elById("wcfUiPageSearchResultList"),u=elById("wcfUiPageSearchResultListContainer")),a.value="",elHide(u),c.innerHTML="",a.focus()},title:""},source:'<div class="section"><dl><dt><label for="wcfUiPageSearchInput">'+e.get("wcf.page.pageObjectID.search.terms")+'</label></dt><dd><input type="text" id="wcfUiPageSearchInput" class="long"></dd></dl></div><section id="wcfUiPageSearchResultListContainer" class="section sectionContainerList"><header class="sectionHeader"><h2 class="sectionTitle">'+e.get("wcf.page.pageObjectID.search.results")+'</h2></header><ul id="wcfUiPageSearchResultList" class="containerList wcfUiPageSearchResultList"></ul></section>'}}}}),define("WoltLabSuite/Core/Ui/Reaction/Profile/Loader",["Ajax","Core","Language"],function(e,t,i){"use strict";function n(e,t){this.init(e,t)}return n.prototype={init:function(e,t){if(this._container=elById("likeList"),this._userID=e,this._reactionTypeID=t,this._targetType="received",this._options={parameters:[]},!this._userID)throw new Error("[WoltLabSuite/Core/Ui/Reaction/Profile/Loader] Invalid parameter 'userID' given.");if(!this._reactionTypeID)throw new Error("[WoltLabSuite/Core/Ui/Reaction/Profile/Loader] Invalid parameter 'firstReactionTypeID' given.");var n=elCreate("li");n.className="likeListMore showMore",this._noMoreEntries=elCreate("small"),this._noMoreEntries.innerHTML=i.get("wcf.like.reaction.noMoreEntries"),this._noMoreEntries.style.display="none",n.appendChild(this._noMoreEntries),this._loadButton=elCreate("button"),this._loadButton.className="small",this._loadButton.innerHTML=i.get("wcf.like.reaction.more"),this._loadButton.addEventListener(WCF_CLICK_EVENT,this._loadReactions.bind(this)),this._loadButton.style.display="none",n.appendChild(this._loadButton),this._container.appendChild(n),2===elBySel("#likeList > li").length?this._noMoreEntries.style.display="":this._loadButton.style.display="",this._setupReactionTypeButtons(),this._setupTargetTypeButtons()},_setupReactionTypeButtons:function(){for(var e,t=elBySelAll("#reactionType .button"),i=0,n=t.length;i<n;i++)e=t[i],e.addEventListener(WCF_CLICK_EVENT,this._changeReactionTypeValue.bind(this,~~elData(e,"reaction-type-id")))},_setupTargetTypeButtons:function(){for(var e,t=elBySelAll("#likeType .button"),i=0,n=t.length;i<n;i++)e=t[i],e.addEventListener(WCF_CLICK_EVENT,this._changeTargetType.bind(this,elData(e,"like-type")))},_changeTargetType:function(e){if("given"!==e&&"received"!==e)throw new Error("[WoltLabSuite/Core/Ui/Reaction/Profile/Loader] Invalid parameter 'targetType' given.");e!==this._targetType&&(elBySel("#likeType .button.active").classList.remove("active"),elBySel('#likeType .button[data-like-type="'+e+'"]').classList.add("active"),this._targetType=e,this._reload())},_changeReactionTypeValue:function(e){this._reactionTypeID!==e&&(elBySel("#reactionType .button.active").classList.remove("active"),elBySel('#reactionType .button[data-reaction-type-id="'+e+'"]').classList.add("active"),this._reactionTypeID=e,this._reload())},_reload:function(){for(var e=elBySelAll("#likeList > li:not(:first-child):not(:last-child)"),t=0,i=e.length;t<i;t++)this._container.removeChild(e[t]);elData(this._container,"last-like-time",0),this._loadReactions()},_loadReactions:function(){this._options.parameters.userID=this._userID,this._options.parameters.lastLikeTime=elData(this._container,"last-like-time"),this._options.parameters.targetType=this._targetType,this._options.parameters.reactionTypeID=this._reactionTypeID,e.api(this,{parameters:this._options.parameters})},_ajaxSuccess:function(e){e.returnValues.template?(elBySel("#likeList > li:nth-last-child(1)").insertAdjacentHTML("beforebegin",e.returnValues.template),elData(this._container,"last-like-time",e.returnValues.lastLikeTime),this._noMoreEntries.style.display="none",this._loadButton.style.display=""):(this._noMoreEntries.style.display="",this._loadButton.style.display="none")},_ajaxSetup:function(){return{data:{actionName:"load",className:"\\wcf\\data\\reaction\\ReactionAction"}}}},n}),define("WoltLabSuite/Core/Ui/User/Activity/Recent",["Ajax","Language","Dom/Util"],function(e,t,i){"use strict";function n(e){this.init(e)}return n.prototype={init:function(e){this._containerId=e;var i=elById(this._containerId);this._list=elBySel(".recentActivityList",i);var n=elCreate("li");n.className="showMore",this._list.childElementCount?(n.innerHTML='<button class="small">'+t.get("wcf.user.recentActivity.more")+"</button>",n.children[0].addEventListener(WCF_CLICK_EVENT,this._showMore.bind(this))):n.innerHTML="<small>"+t.get("wcf.user.recentActivity.noMoreEntries")+"</small>",this._list.appendChild(n),this._showMoreItem=n,elBySelAll(".jsRecentActivitySwitchContext .button",i,function(e){e.addEventListener(WCF_CLICK_EVENT,function(t){t.preventDefault(),e.classList.contains("active")||this._switchContext()}.bind(this))}.bind(this))},_showMore:function(t){t.preventDefault(),this._showMoreItem.children[0].disabled=!0,e.api(this,{actionName:"load",parameters:{boxID:~~elData(this._list,"box-id"),filteredByFollowedUsers:elDataBool(this._list,"filtered-by-followed-users"),lastEventId:elData(this._list,"last-event-id"),lastEventTime:elData(this._list,"last-event-time"),userID:~~elData(this._list,"user-id")}})},_switchContext:function(){e.api(this,{actionName:"switchContext"},function(){window.location.hash="#"+this._containerId,window.location.reload()}.bind(this))},_ajaxSuccess:function(e){e.returnValues.template?(i.insertHtml(e.returnValues.template,this._showMoreItem,"before"),elData(this._list,"last-event-time",e.returnValues.lastEventTime),elData(this._list,"last-event-id",e.returnValues.lastEventID),this._showMoreItem.children[0].disabled=!1):this._showMoreItem.innerHTML="<small>"+t.get("wcf.user.recentActivity.noMoreEntries")+"</small>"},_ajaxSetup:function(){return{data:{className:"wcf\\data\\user\\activity\\event\\UserActivityEventAction"}}}},n}),define("WoltLabSuite/Core/Ui/User/CoverPhoto/Delete",["Ajax","EventHandler","Language","Ui/Confirmation","Ui/Notification"],function(e,t,i,n,o){"use strict";var r,a=0;return{init:function(e){r=elBySel(".jsButtonDeleteCoverPhoto"),r.addEventListener(WCF_CLICK_EVENT,this._click.bind(this)),a=e,t.add("com.woltlab.wcf.user","coverPhoto",function(e){"string"==typeof e.url&&e.url.length>0&&elShow(r.parentNode)})},_click:function(t){t.preventDefault(),n.show({confirm:e.api.bind(e,this),message:i.get("wcf.user.coverPhoto.delete.confirmMessage")})},_ajaxSuccess:function(e){elBySel(".userProfileCoverPhoto").style.setProperty("background-image","url("+e.returnValues.url+")",""),elHide(r.parentNode),o.show()},_ajaxSetup:function(){return{data:{actionName:"deleteCoverPhoto",className:"wcf\\data\\user\\UserProfileAction",parameters:{userID:a}}}}}}),define("WoltLabSuite/Core/Ui/User/CoverPhoto/Upload",["Core","EventHandler","Upload","Ui/Notification","Ui/Dialog"],function(e,t,i,n,o){"use strict";function r(e){i.call(this,"coverPhotoUploadButtonContainer","coverPhotoUploadPreview",{action:"uploadCoverPhoto",className:"wcf\\data\\user\\UserProfileAction"}),this._userId=e}return e.inherit(r,i,{_getParameters:function(){return{userID:this._userId}},_success:function(e,i){elInnerError(this._button,i.returnValues.errorMessage),this._target.innerHTML="",i.returnValues.url&&(elBySel(".userProfileCoverPhoto").style.setProperty("background-image","url("+i.returnValues.url+")",""),o.close("userProfileCoverPhotoUpload"),n.show(),t.fire("com.woltlab.wcf.user","coverPhoto",{url:i.returnValues.url}))}}),r}),define("WoltLabSuite/Core/Ui/User/Trophy/List",["Ajax","Core","Dictionary","Dom/Util","Ui/Dialog","WoltLabSuite/Core/Ui/Pagination","Dom/ChangeListener","List"],function(e,t,i,n,o,r,a,s){"use strict";function l(){this.init()}return l.prototype={init:function(){this._cache=new i,this._knownElements=new s,this._options={className:"wcf\\data\\user\\trophy\\UserTrophyAction",parameters:{}},this._rebuild(),a.add("WoltLabSuite/Core/Ui/User/Trophy/List",this._rebuild.bind(this))},_rebuild:function(){elBySelAll(".userTrophyOverlayList",void 0,function(e){this._knownElements.has(e)||(e.addEventListener(WCF_CLICK_EVENT,this._open.bind(this,elData(e,"user-id"))),this._knownElements.add(e))}.bind(this))},_open:function(e,t){t.preventDefault(),this._currentPageNo=1,this._currentUser=e,this._showPage()},_showPage:function(t){if(void 0!==t&&(this._currentPageNo=t),this._cache.has(this._currentUser)){if(0!==this._cache.get(this._currentUser).get("pageCount")&&(this._currentPageNo<1||this._currentPageNo>this._cache.get(this._currentUser).get("pageCount")))throw new RangeError("pageNo must be between 1 and "+this._cache.get(this._currentUser).get("pageCount")+" ("+this._currentPageNo+" given).")}else this._cache.set(this._currentUser,new i);if(this._cache.get(this._currentUser).has(this._currentPageNo)){var n=o.open(this,this._cache.get(this._currentUser).get(this._currentPageNo));if(o.setTitle("userTrophyListOverlay",this._cache.get(this._currentUser).get("title")),this._cache.get(this._currentUser).get("pageCount")>1){var a=elBySel(".jsPagination",n.content);null!==a&&new r(a,{activePage:this._currentPageNo,maxPage:this._cache.get(this._currentUser).get("pageCount"),callbackSwitch:this._showPage.bind(this)})}}else this._options.parameters.pageNo=this._currentPageNo,this._options.parameters.userID=this._currentUser,e.api(this,{parameters:this._options.parameters})},_ajaxSuccess:function(e){void 0!==e.returnValues.pageCount&&this._cache.get(this._currentUser).set("pageCount",~~e.returnValues.pageCount),this._cache.get(this._currentUser).set(this._currentPageNo,e.returnValues.template),this._cache.get(this._currentUser).set("title",e.returnValues.title),this._showPage()},_ajaxSetup:function(){return{data:{actionName:"getGroupedUserTrophyList",className:this._options.className}}},_dialogSetup:function(){return{id:"userTrophyListOverlay",options:{title:""},source:null}}},l}),define("WoltLabSuite/Core/Form/Builder/Field/Controller/Label",["Core","Dom/Util","Language","Ui/SimpleDropdown"],function(e,t,i,n){"use strict";function o(e,t,i){this.init(e,t,i)}return o.prototype={init:function(o,r,a){this._formFieldContainer=elById(o+"Container"),this._labelChooser=elByClass("labelChooser",this._formFieldContainer)[0],this._options=e.extend({forceSelection:!1,showWithoutSelection:!1},a),this._input=elCreate("input"),this._input.type="hidden",this._input.id=o,this._input.name=o,this._input.value=~~r,this._formFieldContainer.appendChild(this._input);var s=t.identify(this._labelChooser),l=n.getDropdownMenu(s);null===l&&(n.init(elByClass("dropdownToggle",this._labelChooser)[0]),l=n.getDropdownMenu(s));var c=null;if(this._options.showWithoutSelection||!this._options.forceSelection){c=elCreate("ul"),l.appendChild(c);var u=elCreate("li");u.className="dropdownDivider",c.appendChild(u)}if(this._options.showWithoutSelection){var d=elCreate("li");elData(d,"label-id",-1),this._blockScroll(d),c.appendChild(d);var h=elCreate("span");d.appendChild(h);var f=elCreate("span");f.className="badge label",f.innerHTML=i.get("wcf.label.withoutSelection"),h.appendChild(f)}if(!this._options.forceSelection){var d=elCreate("li");elData(d,"label-id",0),this._blockScroll(d),c.appendChild(d);var h=elCreate("span");d.appendChild(h);var f=elCreate("span");f.className="badge label",f.innerHTML=i.get("wcf.label.none"),h.appendChild(f)}elBySelAll("li:not(.dropdownDivider)",l,function(e){e.addEventListener("click",this._click.bind(this)),r&&~~elData(e,"label-id")===r&&this._selectLabel(e)}.bind(this))},_blockScroll:function(e){e.addEventListener("wheel",function(e){e.preventDefault()},{passive:!1})},_click:function(e){e.preventDefault(),this._selectLabel(e.currentTarget,!1)},_selectLabel:function(e){var t=elData(e,"label-id");t||(t=0);var i=elBySel("span > span",e),n=elBySel(".dropdownToggle > span",this._labelChooser);n.className=i.className,n.textContent=i.textContent,this._input.value=t}},o}),define("WoltLabSuite/Core/Form/Builder/Field/Controller/Rating",["Dictionary","Environment"],function(e,t){"use strict";function i(e,t,i,n){this.init(e,t,i,n)}return i.prototype={init:function(t,i,n,o){if(this._field=elBySel("#"+t+"Container"),null===this._field)throw new Error("Unknown field with id '"+t+"'");this._input=elCreate("input"),this._input.id=t,this._input.name=t,this._input.type="hidden",this._input.value=i,this._field.appendChild(this._input),this._activeCssClasses=n,this._defaultCssClasses=o,this._ratingElements=new e;var r=elBySel(".ratingList",this._field);r.addEventListener("mouseleave",this._restoreRating.bind(this)),elBySelAll("li",r,function(e){e.classList.contains("ratingMetaButton")?(e.addEventListener("click",this._metaButtonClick.bind(this)),e.addEventListener("mouseenter",this._restoreRating.bind(this))):(this._ratingElements.set(~~elData(e,"rating"),e),e.addEventListener("click",this._listItemClick.bind(this)),e.addEventListener("mouseenter",this._listItemMouseEnter.bind(this)),e.addEventListener("mouseleave",this._listItemMouseLeave.bind(this)))}.bind(this))},_listItemClick:function(e){this._input.value=~~elData(e.currentTarget,"rating"),"desktop"!==t.platform()&&this._restoreRating()},_listItemMouseEnter:function(e){var t=elData(e.currentTarget,"rating");this._ratingElements.forEach(function(e,i){var n=elByClass("icon",e)[0];this._toggleIcon(n,~~i<=~~t)}.bind(this))},_listItemMouseLeave:function(){this._ratingElements.forEach(function(e){var t=elByClass("icon",e)[0];this._toggleIcon(t,!1)}.bind(this))},_metaButtonClick:function(e){"removeRating"===elData(e.currentTarget,"action")&&(this._input.value="",this._listItemMouseLeave())},_restoreRating:function(){this._ratingElements.forEach(function(e,t){var i=elByClass("icon",e)[0];this._toggleIcon(i,~~t<=~~this._input.value)}.bind(this))},_toggleIcon:function(e,t){if(t=t||!1){for(var i=0;i<this._defaultCssClasses.length;i++)e.classList.remove(this._defaultCssClasses[i]);for(var i=0;i<this._activeCssClasses.length;i++)e.classList.add(this._activeCssClasses[i])}else{for(var i=0;i<this._activeCssClasses.length;i++)e.classList.remove(this._activeCssClasses[i]);for(var i=0;i<this._defaultCssClasses.length;i++)e.classList.add(this._defaultCssClasses[i])}}},i}),define("WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract",["./Manager"],function(e){"use strict";function t(e,t){this.init(e,t)}return t.prototype={checkDependency:function(){throw new Error("Missing implementation of WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract.checkDependency!")},getDependentNode:function(){return this._dependentElement},getField:function(){return this._field},getFields:function(){return this._fields},init:function(t,i){if(this._dependentElement=elById(t),null===this._dependentElement)throw new Error("Unknown dependent element with container id '"+t+"Container'.");if(this._field=elById(i),null===this._field){if(this._fields=[],elBySelAll("input[type=radio][name="+i+"]",void 0,function(e){this._fields.push(e)}.bind(this)),!this._fields.length)throw new Error("Unknown field with id '"+i+"'.")}else if(this._fields=[this._field],"INPUT"===this._field.tagName&&"radio"===this._field.type&&""!==elData(this._field,"no-input-id")){if(this._noField=elById(elData(this._field,"no-input-id")),null===this._noField)throw new Error("Cannot find 'no' input field for input field '"+i+"'");this._fields.push(this._noField)}e.addDependency(this)}},t}),define("WoltLabSuite/Core/Form/Builder/Field/Dependency/NonEmpty",["./Abstract","Core"],function(e,t){"use strict";function i(e,t){this.init(e,t)}return t.inherit(i,e,{checkDependency:function(){switch(this._field.tagName){case"INPUT":switch(this._field.type){case"checkbox":return this._field.checked;case"radio":return(!this._noField||!this._noField.checked)&&this._field.checked;default:return 0!==this._field.value.trim().length}case"SELECT":return 0!==this._field.value.length;case"TEXTAREA":return 0!==this._field.value.trim().length}}}),i}),define("WoltLabSuite/Core/Form/Builder/Field/Dependency/Value",["./Abstract","Core","./Manager"],function(e,t,i){"use strict";function n(e,t,i){this.init(e,t),this._isNegated=!1}return t.inherit(n,e,{checkDependency:function(){if(!this._values)throw new Error("Values have not been set.");var e;if(this._field){if(i.isHiddenByDependencies(this._field))return!1;e=this._field.value}else for(var t,n=0,o=this._fields.length;n<o;n++)if(t=this._fields[n],t.checked){if(i.isHiddenByDependencies(t))return!1;e=t.value;break}for(var n=0,o=this._values.length;n<o;n++)if(this._values[n]==e)return!this._isNegated;return!!this._isNegated},negate:function(e){return this._isNegated=e,this},values:function(e){return this._values=e,this}}),n}),define("WoltLabSuite/Core/Form/Builder/Field/Language/ContentLanguage",["Core","WoltLabSuite/Core/Language/Chooser","../Value"],function(e,t,i){"use strict";function n(e){this.init(e)}return e.inherit(n,i,{destroy:function(){t.removeChooser(this._fieldId)}}),n}),define("WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/Abstract",["EventHandler","../Manager"],function(e,t){"use strict";function i(e){this.init(e)}return i.prototype={checkContainer:function(){throw new Error("Missing implementation of WoltLabSuite/Core/Form/Builder/Field/Dependency/Container.checkContainer!")},init:function(e){if("string"!=typeof e)throw new TypeError("Container id has to be a string.");if(this._container=elById(e),null===this._container)throw new Error("Unknown container with id '"+e+"'.");t.addContainerCheckCallback(this.checkContainer.bind(this))}},i}),define("WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/Default",["./Abstract","Core","../Manager"],function(e,t,i){"use strict";function n(e){this.init(e)}return t.inherit(n,e,{checkContainer:function(){if(!elDataBool(this._container,"ignore-dependencies")&&!i.isHiddenByDependencies(this._container)){var e=!elIsHidden(this._container),t=!1,n=this._container.children,o=0;if("H2"===this._container.children.item(0).tagName||"HEADER"===this._container.children.item(0).tagName)var o=1;for(var r=o,a=n.length;r<a;r++)if(!elIsHidden(n.item(r))){t=!0;break}e!==t&&(t?elShow(this._container):elHide(this._container),i.checkContainers())}}}),n}),define("WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/Tab",["./Abstract","Core","Dom/Util","../Manager","Ui/TabMenu"],function(e,t,i,n,o){"use strict";function r(e){this.init(e)}return t.inherit(r,e,{checkContainer:function(){if(!n.isHiddenByDependencies(this._container)){for(var e=!elIsHidden(this._container),t=!1,r=this._container.children,a=0,s=r.length;a<s;a++)if(!elIsHidden(r.item(a))){t=!0;break}if(e!==t){var l=elBySel("#"+i.identify(this._container.parentNode)+" > nav > ul > li[data-name="+this._container.id+"]",this._container.parentNode.parentNode);if(null===l)throw new Error("Cannot find tab menu entry for tab '"+this._container.id+"'.");if(t)elShow(this._container),elShow(l);else{elHide(this._container),elHide(l);var c=o.getTabMenu(i.identify(l.closest(".tabMenuContainer")));c.getActiveTab()===l&&c.selectFirstVisible()}n.checkContainers()}}}}),r}),define("WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/TabMenu",["./Abstract","Core","Dom/Util","../Manager","Ui/TabMenu"],function(e,t,i,n,o){"use strict";function r(e){this.init(e)}return t.inherit(r,e,{checkContainer:function(){if(!n.isHiddenByDependencies(this._container)){for(var e=!elIsHidden(this._container),t=!1,r=elBySelAll("#"+i.identify(this._container)+" > nav > ul > li",this._container.parentNode),a=0,s=r.length;a<s;a++)if(!elIsHidden(r[a])){t=!0;break}e!==t&&(t?(elShow(this._container),o.getTabMenu(i.identify(this._container)).selectFirstVisible()):elHide(this._container),n.checkContainers())}}}),r}),define("WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/AbstractPackageList",["Dom/ChangeListener","Dom/Traverse","Dom/Util","EventKey","Language"],function(e,t,i,n,o){"use strict";function r(e,t){this.init(e,t)}return r.prototype={init:function(e,t){if(this._formFieldId=e,this._packageList=elById(this._formFieldId+"_packageList"),null===this._packageList)throw new Error("Cannot find package list for packages field with id '"+this._formFieldId+"'.");if(this._packageIdentifier=elById(this._formFieldId+"_packageIdentifier"),null===this._packageIdentifier)throw new Error("Cannot find package identifier form field for packages field with id '"+this._formFieldId+"'.");if(this._packageIdentifier.addEventListener("keypress",this._keyPress.bind(this)),this._addButton=elById(this._formFieldId+"_addButton"),null===this._addButton)throw new Error("Cannot find add button for packages field with id '"+this._formFieldId+"'.");if(this._addButton.addEventListener("click",this._addPackage.bind(this)),this._form=this._packageList.closest("form"),null===this._form)throw new Error("Cannot find form element for packages field with id '"+this._formFieldId+"'.");this._form.addEventListener("submit",this._submit.bind(this)),t.forEach(this._addPackageByData.bind(this))},_addPackage:function(e){e.preventDefault(),e.stopPropagation(),this._validateInput()&&(this._addPackageByData(this._getInputData()),this._emptyInput(),this._packageIdentifier.focus())},_addPackageByData:function(t){var n=elCreate("li");this._populateListItem(n,t);var r=elCreate("span");r.className="icon icon16 fa-times pointer jsTooltip",elAttr(r,"title",o.get("wcf.global.button.delete")),r.addEventListener("click",this._removePackage.bind(this)),i.prepend(r,n),this._packageList.appendChild(n),e.trigger()},_createSubmitFields:function(e,t){var i=elCreate("input");elAttr(i,"type","hidden"),elAttr(i,"name",this._formFieldId+"["+t+"][packageIdentifier]"),i.value=elData(e,"package-identifier"),this._form.appendChild(i)},_emptyInput:function(){this._packageIdentifier.value=""},_getErrorElement:function(e,n){var o=t.nextByClass(e,"innerError");return null===o&&n&&(o=elCreate("small"),o.className="innerError",i.insertAfter(o,e)),o},_getInputData:function(){return{packageIdentifier:this._packageIdentifier.value}},_getPackageIdentifierErrorElement:function(e){return this._getErrorElement(this._packageIdentifier,e)},_keyPress:function(e){n.Enter(e)&&this._addPackage(e)},_populateListItem:function(e,t){elData(e,"package-identifier",t.packageIdentifier)},_removePackage:function(e){elRemove(e.currentTarget.closest("li")),!this._packageList.childElementCount&&"SMALL"===this._packageList.nextElementSibling.tagName&&this._packageList.nextElementSibling.classList.contains("innerError")&&elRemove(this._packageList.nextElementSibling)},_submit:function(){t.childrenByTag(this._packageList,"LI").forEach(this._createSubmitFields.bind(this))},_validateInput:function(){return this._validatePackageIdentifier()},_validatePackageIdentifier:function(){var e=this._packageIdentifier.value;if(""===e)return this._getPackageIdentifierErrorElement(!0).textContent=o.get("wcf.global.form.error.empty"),!1;if(e.length<3)return this._getPackageIdentifierErrorElement(!0).textContent=o.get("wcf.acp.devtools.project.packageIdentifier.error.minimumLength"),!1;if(e.length>191)return this._getPackageIdentifierErrorElement(!0).textContent=o.get("wcf.acp.devtools.project.packageIdentifier.error.maximumLength"),!1;if(!e.match(/^[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/))return this._getPackageIdentifierErrorElement(!0).textContent=o.get("wcf.acp.devtools.project.packageIdentifier.error.format"),!1;var i=!1;if(t.childrenByTag(this._packageList,"LI").forEach(function(t,n){elData(t,"package-identifier")===e&&(i=!0)}),i)return this._getPackageIdentifierErrorElement(!0).textContent=o.get("wcf.acp.devtools.project.packageIdentifier.error.duplicate"),!1;var n=this._getPackageIdentifierErrorElement();return null!==n&&elRemove(n),!0},_validateVersion:function(e,t){if(""!==e){if(e.length>255)return t(!0).textContent=o.get("wcf.acp.devtools.project.packageVersion.error.maximumLength"),!1;if(!e.match(/^([0-9]+)\.([0-9]+)\.([0-9]+)(\ (a|alpha|b|beta|d|dev|rc|pl)\ ([0-9]+))?$/i))return t(!0).textContent=o.get("wcf.acp.devtools.project.packageVersion.error.format"),!1}var i=t();return null!==i&&elRemove(i),!0}},r}),define("WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/ExcludedPackages",["./AbstractPackageList","Core","Language"],function(e,t,i){"use strict";function n(e,t){this.init(e,t)}return t.inherit(n,e,{init:function(e,t){if(n._super.prototype.init.call(this,e,t),this._version=elById(this._formFieldId+"_version"),null===this._version)throw new Error("Cannot find version form field for packages field with id '"+this._formFieldId+"'.");this._version.addEventListener("keypress",this._keyPress.bind(this))},_createSubmitFields:function(e,t){n._super.prototype._createSubmitFields.call(this,e,t);var i=elCreate("input");elAttr(i,"type","hidden"),elAttr(i,"name",this._formFieldId+"["+t+"][version]"),i.value=elData(e,"version"),this._form.appendChild(i)},_emptyInput:function(){n._super.prototype._emptyInput.call(this),this._version.value=""},_getInputData:function(){return t.extend(n._super.prototype._getInputData.call(this),{version:this._version.value})},_getVersionErrorElement:function(e){return this._getErrorElement(this._version,e)},_populateListItem:function(e,t){n._super.prototype._populateListItem.call(this,e,t),elData(e,"version",t.version),e.innerHTML=" "+i.get("wcf.acp.devtools.project.excludedPackage.excludedPackage",{packageIdentifier:t.packageIdentifier,version:t.version})},_validateInput:function(){return n._super.prototype._validateInput.call(this)&&this._validateVersion(this._version.value,this._getVersionErrorElement.bind(this))}}),n}),define("WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/Instructions",["Dom/ChangeListener","Dom/Traverse","Dom/Util","EventKey","Language","Ui/Confirmation","Ui/Dialog","WoltLabSuite/Core/Ui/Sortable/List"],function(e,t,i,n,o,r,a,s){"use strict";function l(e,t,i,n,o,r){this.init(e,t,i,n,o,r||[])}var c=["acpTemplate","file","script","template"];return l.prototype={init:function(t,i,n,o,r,a){if(this._formFieldId=t,this._instructionsTemplate=i,this._instructionsEditDialogTemplate=n,this._instructionEditDialogTemplate=o,this._instructionsCounter=0,this._pipDefaultFilenames=r,this._instructionCounter=0,this._instructionsList=elById(this._formFieldId+"_instructionsList"),null===this._instructionsList)throw new Error("Cannot find package list for packages field with id '"+this._formFieldId+"'.");if(this._instructionsType=elById(this._formFieldId+"_instructionsType"),null===this._instructionsType)throw new Error("Cannot find instruction type form field for instructions field with id '"+this._formFieldId+"'.");if(this._instructionsType.addEventListener("change",this._toggleFromVersionFormField.bind(this)),this._fromVersion=elById(this._formFieldId+"_fromVersion"),null===this._fromVersion)throw new Error("Cannot find from version form field for instructions field with id '"+this._formFieldId+"'.");if(this._fromVersion.addEventListener("keypress",this._instructionsKeyPress.bind(this)),this._addButton=elById(this._formFieldId+"_addButton"),null===this._addButton)throw new Error("Cannot find add button for instructions field with id '"+this._formFieldId+"'.")
+;if(this._addButton.addEventListener("click",this._addInstructions.bind(this)),this._form=this._instructionsList.closest("form"),null===this._form)throw new Error("Cannot find form element for instructions field with id '"+this._formFieldId+"'.");this._form.addEventListener("submit",this._submit.bind(this));var s=!1;for(var l in a){if("install"===a[l].type){s=!0;break}}s||this._addInstructionsByData({fromVersion:"",type:"install"}),a.forEach(this._addInstructionsByData.bind(this)),e.trigger()},_addInstruction:function(t){t.preventDefault(),t.stopPropagation();var i=elData(t.currentTarget.closest("li.section"),"instructions-id"),n=elById(this._formFieldId+"_instructions"+i+"_pip");if(n.value){var r=elById(this._formFieldId+"_instructions"+i+"_value"),a=elById(this._formFieldId+"_instructions"+i+"_runStandalone"),s=elById(this._formFieldId+"_instructions"+i+"_application");this._addInstructionByData(i,{application:-1!==c.indexOf(n.value)?s.value:"",pip:n.value,runStandalone:~~a.checked,value:r.value}),n.value="",r.value="",a.checked=!1,s.value="",elById(this._formFieldId+"_instructions"+i+"_valueDescription").innerHTML=o.get("wcf.acp.devtools.project.instruction.value.description"),this._toggleApplicationFormField(i),e.trigger()}},_addInstructionByData:function(e,t){var i=++this._instructionCounter,n=elById(this._formFieldId+"_instructions"+e+"_instructionList"),r=elCreate("li");r.className="sortableNode",r.id=this._formFieldId+"_instruction"+i,elData(r,"instruction-id",i),elData(r,"application",t.application),elData(r,"pip",t.pip),elData(r,"runStandalone",t.runStandalone),elData(r,"value",t.value);var a='<div class="sortableNodeLabel">\t<div class="jsDevtoolsProjectInstruction">\t\t'+o.get("wcf.acp.devtools.project.instruction.instruction",t);if(t.errors)for(var s in t.errors)a+='<small class="innerError">'+t.errors[s]+"</small>";a+='\t</div>\t<span class="statusDisplay sortableButtonContainer">\t\t<span class="icon icon16 fa-pencil pointer jsTooltip" id="'+this._formFieldId+"_instruction"+i+'_editButton" title="'+o.get("wcf.global.button.edit")+'"></span>\t\t<span class="icon icon16 fa-times pointer jsTooltip" id="'+this._formFieldId+"_instruction"+i+'_deleteButton" title="'+o.get("wcf.global.button.delete")+'"></span>\t</span></div>',r.innerHTML=a,n.appendChild(r),elById(this._formFieldId+"_instruction"+i+"_deleteButton").addEventListener("click",this._removeInstruction.bind(this)),elById(this._formFieldId+"_instruction"+i+"_editButton").addEventListener("click",this._editInstruction.bind(this))},_addInstructions:function(t){t.preventDefault(),t.stopPropagation(),this._validateInstructionsType()&&("update"!==this._instructionsType.value||this._validateFromVersion(this._fromVersion))&&(this._addInstructionsByData({fromVersion:"update"===this._instructionsType.value?this._fromVersion.value:"",type:this._instructionsType.value}),this._instructionsType.value="",this._fromVersion.value="",this._toggleFromVersionFormField(),e.trigger())},_addInstructionsByData:function(e){var t=++this._instructionsCounter,i=elCreate("li");i.className="section",i.innerHTML=this._instructionsTemplate.fetch({instructionsId:t,sectionTitle:o.get("wcf.acp.devtools.project.instructions.type."+e.type+".title",{fromVersion:e.fromVersion}),type:e.type}),i.id=this._formFieldId+"_instructions"+t,elData(i,"instructions-id",t),elData(i,"type",e.type),elData(i,"fromVersion",e.fromVersion),elById(this._formFieldId+"_instructions"+t+"_valueDescription"),this._instructionsList.appendChild(i);var n=elById(this._formFieldId+"_instructions"+t+"_instructionListContainer");Array.isArray(e.errors)&&e.errors.forEach(function(e){var t=elCreate("small");t.className="innerError",t.innerHTML=e,n.parentNode.insertBefore(t,n)}),new s({containerId:n.id,isSimpleSorting:!0,options:{toleranceElement:"> div"}});elById(this._formFieldId+"_instructions"+t+"_deleteButton");if("update"===e.type&&(elById(this._formFieldId+"_instructions"+t+"_deleteButton").addEventListener("click",this._removeInstructions.bind(this)),elById(this._formFieldId+"_instructions"+t+"_editButton").addEventListener("click",this._editInstructions.bind(this))),elById(this._formFieldId+"_instructions"+t+"_pip").addEventListener("change",this._changeInstructionPip.bind(this)),elById(this._formFieldId+"_instructions"+t+"_value").addEventListener("keypress",this._instructionKeyPress.bind(this)),elById(this._formFieldId+"_instructions"+t+"_addButton").addEventListener("click",this._addInstruction.bind(this)),e.instructions)for(var r in e.instructions)this._addInstructionByData(t,e.instructions[r])},_changeInstructionPip:function(e){var t=e.currentTarget.value,i=elData(e.currentTarget.closest("li.section"),"instructions-id"),n=elById(this._formFieldId+"_instructions"+i+"_valueDescription");""!==this._pipDefaultFilenames[t]?n.innerHTML=o.get("wcf.acp.devtools.project.instruction.value.description.defaultFilename",{defaultFilename:this._pipDefaultFilenames[t]}):n.innerHTML=o.get("wcf.acp.devtools.project.instruction.value.description");elById(this._formFieldId+"_instructions"+i+"_value").closest("dl").classList,elById(this._formFieldId+"_instructions"+i+"_application").closest("dl");this._toggleApplicationFormField(i)},_editInstruction:function(i){var r=i.currentTarget.closest("li"),s=elData(r,"instruction-id"),l=elData(r,"application"),u=elData(r,"pip"),d=elDataBool(r,"runStandalone"),h=elData(r,"value"),f=this._instructionEditDialogTemplate.fetch({runStandalone:d,value:h}),p="instructionEditDialog"+s;a.getDialog(p)?a.openStatic(p):a.openStatic(p,f,{onSetup:function(i){var r=elBySel("select[name=application]",i),d=elBySel("select[name=pip]",i),h=elBySel("input[name=runStandalone]",i),f=elBySel("input[name=value]",i);r.value=l,d.value=u;var g=function(){var t=elById(this._formFieldId+"_instruction"+s);elData(t,"application",-1!==c.indexOf(d.value)?r.value:""),elData(t,"pip",d.value),elData(t,"runStandalone",~~h.checked),elData(t,"value",f.value),elByClass("jsDevtoolsProjectInstruction",t)[0].innerHTML=o.get("wcf.acp.devtools.project.instruction.instruction",{application:elData(t,"application"),pip:elData(t,"pip"),runStandalone:elDataBool(t,"runStandalone"),value:elData(t,"value")}),e.trigger(),a.close(p)}.bind(this);f.addEventListener("keypress",function(e){n.Enter(e)&&g()}),elBySel("button[data-type=submit]",i).addEventListener("click",g);var m=function(){var e=d.value;-1!==c.indexOf(e)?elShow(r.closest("dl")):elHide(r.closest("dl"));var i=t.nextByTag(f,"SMALL");""!==this._pipDefaultFilenames[e]?i.innerHTML=o.get("wcf.acp.devtools.project.instruction.value.description.defaultFilename",{defaultFilename:this._pipDefaultFilenames[e]}):i.innerHTML=o.get("wcf.acp.devtools.project.instruction.value.description")}.bind(this);d.addEventListener("change",m),m()}.bind(this),title:o.get("wcf.acp.devtools.project.instruction.edit")})},_editInstructions:function(t){var i=t.currentTarget.closest("li"),r=elData(i,"instructions-id"),s=elData(i,"fromVersion"),l=this._instructionsEditDialogTemplate.fetch({fromVersion:s}),c="instructionsEditDialog"+r;a.getDialog(c)?a.openStatic(c):a.openStatic(c,l,{onSetup:function(t){var i=elBySel("input[name=fromVersion]",t),s=function(){if(this._validateFromVersion(i)){var t=elById(this._formFieldId+"_instructions"+r);elData(t,"fromVersion",i.value),elByClass("jsInstructionsTitle",t)[0].textContent=o.get("wcf.acp.devtools.project.instructions.type.update.title",{fromVersion:i.value}),e.trigger(),a.close(c)}}.bind(this);i.addEventListener("keypress",function(e){n.Enter(e)&&s()}),elBySel("button[data-type=submit]",t).addEventListener("click",s)}.bind(this),title:o.get("wcf.acp.devtools.project.instructions.edit")})},_getErrorElement:function(e,n){var o=t.nextByClass(e,"innerError");return null===o&&n&&(o=elCreate("small"),o.className="innerError",i.insertAfter(o,e)),o},_getFromVersionErrorElement:function(e,t){return this._getErrorElement(e,t)},_getInstructionsTypeErrorElement:function(e){return this._getErrorElement(this._instructionsType,e)},_instructionKeyPress:function(e){n.Enter(e)&&this._addInstruction(e)},_instructionsKeyPress:function(e){n.Enter(e)&&this._addInstructions(e)},_removeInstruction:function(e){var t=e.currentTarget.closest("li");r.show({confirm:function(){elRemove(t)},message:o.get("wcf.acp.devtools.project.instruction.delete.confirmMessages")})},_removeInstructions:function(e){var t=e.currentTarget.closest("li");r.show({confirm:function(){elRemove(t)},message:o.get("wcf.acp.devtools.project.instructions.delete.confirmMessages")})},_submit:function(e){t.childrenByTag(this._instructionsList,"LI").forEach(function(e,i){var n=this._formFieldId+"["+i+"]",o=elCreate("input");if(elAttr(o,"type","hidden"),elAttr(o,"name",n+"[type]"),o.value=elData(e,"type"),this._form.appendChild(o),"update"===o.value){var r=elCreate("input");elAttr(r,"type","hidden"),elAttr(r,"name",this._formFieldId+"["+i+"][fromVersion]"),r.value=elData(e,"fromVersion"),this._form.appendChild(r)}t.childrenByTag(elById(e.id+"_instructionList"),"LI").forEach(function(e,t){var n=this._formFieldId+"["+i+"][instructions]["+t+"]";if(["pip","value","runStandalone"].forEach(function(t){var i=elCreate("input");elAttr(i,"type","hidden"),elAttr(i,"name",n+"["+t+"]"),i.value=elData(e,t),this._form.appendChild(i)}.bind(this)),-1!==c.indexOf(elData(e,"pip"))){var o=elCreate("input");elAttr(o,"type","hidden"),elAttr(o,"name",n+"[application]"),o.value=elData(e,"application"),this._form.appendChild(o)}}.bind(this))}.bind(this))},_toggleApplicationFormField:function(e){var t=elById(this._formFieldId+"_instructions"+e+"_pip").value,i=elById(this._formFieldId+"_instructions"+e+"_value").closest("dl").classList,n=elById(this._formFieldId+"_instructions"+e+"_application").closest("dl");-1!==c.indexOf(t)?(i.remove("col-md-9"),i.add("col-md-7"),elShow(n)):(i.remove("col-md-7"),i.add("col-md-9"),elHide(n))},_toggleFromVersionFormField:function(){var e=this._instructionsType.closest("dl").classList,t=this._fromVersion.closest("dl");"update"===this._instructionsType.value?(e.remove("col-md-10"),e.add("col-md-5"),elShow(t)):(e.remove("col-md-5"),e.add("col-md-10"),elHide(t))},_validateFromVersion:function(e){var t=e.value;if(""===t)return this._getFromVersionErrorElement(e,!0).textContent=o.get("wcf.global.form.error.empty"),!1;if(t.length>50)return this._getFromVersionErrorElement(e,!0).textContent=o.get("wcf.acp.devtools.project.packageVersion.error.maximumLength"),!1;if(-1===t.indexOf("*")){if(!t.match(/^([0-9]+)\.([0-9]+)\.([0-9]+)(\ (a|alpha|b|beta|d|dev|rc|pl)\ ([0-9]+))?$/i))return this._getFromVersionErrorElement(e,!0).textContent=o.get("wcf.acp.devtools.project.packageVersion.error.format"),!1}else if(!t.replace("*","0").match(/^([0-9]+)\.([0-9]+)\.([0-9]+)(\ (a|alpha|b|beta|d|dev|rc|pl)\ ([0-9]+))?$/i))return this._getFromVersionErrorElement(e,!0).textContent=o.get("wcf.acp.devtools.project.packageVersion.error.format"),!1;var i=this._getFromVersionErrorElement(e);return null!==i&&elRemove(i),!0},_validateInstructionsType:function(){if("install"!==this._instructionsType.value&&"update"!==this._instructionsType.value)return""===this._instructionsType.value?this._getInstructionsTypeErrorElement(!0).textContent=o.get("wcf.global.form.error.empty"):this._getInstructionsTypeErrorElement(!0).textContent=o.get("wcf.global.form.error.noValidSelection"),!1;if("install"===this._instructionsType.value){var e=!1;if([].forEach.call(this._instructionsList.children,function(t){"install"===elData(t,"type")&&(e=!0)}),e)return this._getInstructionsTypeErrorElement(!0).textContent=o.get("wcf.acp.devtools.project.instructions.type.update.error.duplicate"),!1}var t=this._getInstructionsTypeErrorElement();return null!==t&&elRemove(t),!0}},l}),define("WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/OptionalPackages",["./AbstractPackageList","Core","Language"],function(e,t,i){"use strict";function n(e,t){this.init(e,t)}return t.inherit(n,e,{_populateListItem:function(e,t){n._super.prototype._populateListItem.call(this,e,t),e.innerHTML=" "+i.get("wcf.acp.devtools.project.optionalPackage.optionalPackage",{file:t.file,packageIdentifier:t.packageIdentifier})}}),n}),define("WoltLabSuite/Core/Form/Builder/Field/Devtools/Project/RequiredPackages",["./AbstractPackageList","Core","Language"],function(e,t,i){"use strict";function n(e,t){this.init(e,t)}return t.inherit(n,e,{init:function(e,t){if(n._super.prototype.init.call(this,e,t),this._minVersion=elById(this._formFieldId+"_minVersion"),null===this._minVersion)throw new Error("Cannot find minimum version form field for packages field with id '"+this._formFieldId+"'.");if(this._minVersion.addEventListener("keypress",this._keyPress.bind(this)),this._file=elById(this._formFieldId+"_file"),null===this._file)throw new Error("Cannot find file form field for required field with id '"+this._formFieldId+"'.")},_createSubmitFields:function(e,t){n._super.prototype._createSubmitFields.call(this,e,t);var i=elCreate("input");elAttr(i,"type","hidden"),elAttr(i,"name",this._formFieldId+"["+t+"][minVersion]"),i.value=elData(e,"min-version"),this._form.appendChild(i);var o=elCreate("input");elAttr(o,"type","hidden"),elAttr(o,"name",this._formFieldId+"["+t+"][file]"),o.value=elData(e,"file"),this._form.appendChild(o)},_emptyInput:function(){n._super.prototype._emptyInput.call(this),this._minVersion.value="",this._file.checked=!1},_getInputData:function(){return t.extend(n._super.prototype._getInputData.call(this),{file:this._file.checked,minVersion:this._minVersion.value})},_getMinVersionErrorElement:function(e){return this._getErrorElement(this._minVersion,e)},_populateListItem:function(e,t){n._super.prototype._populateListItem.call(this,e,t),elData(e,"min-version",t.minVersion),elData(e,"file",~~t.file),e.innerHTML=" "+i.get("wcf.acp.devtools.project.requiredPackage.requiredPackage",{file:~~t.file,minVersion:t.minVersion,packageIdentifier:t.packageIdentifier})},_validateInput:function(){return n._super.prototype._validateInput.call(this)&&this._validateVersion(this._minVersion.value,this._getMinVersionErrorElement.bind(this))}}),n}),define("WoltLabSuite/Core/Ui/User/Profile/Menu/Item/Abstract",["Ajax","Dom/Util"],function(e,t){"use strict";function i(e,t){}return i.prototype={init:function(e,t){this._userId=e,this._isActive=!1!==t,this._initButton(),this._updateButton()},_initButton:function(){var e=elCreate("a");e.href="#",e.addEventListener(WCF_CLICK_EVENT,this._toggle.bind(this));var i=elCreate("li");i.appendChild(e);var n=elBySel('.userProfileButtonMenu[data-menu="interaction"]');t.prepend(i,n),this._button=e,this._listItem=i},_toggle:function(t){t.preventDefault(),e.api(this,{actionName:this._getAjaxActionName(),parameters:{data:{userID:this._userId}}})},_updateButton:function(){this._button.textContent=this._getLabel(),this._listItem.classList[this._isActive?"add":"remove"]("active")},_getLabel:function(){throw new Error("Implement me!")},_getAjaxActionName:function(){throw new Error("Implement me!")},_ajaxSuccess:function(){throw new Error("Implement me!")},_ajaxSetup:function(){throw new Error("Implement me!")}},i}),define("WoltLabSuite/Core/Ui/User/Profile/Menu/Item/Follow",["Core","Language","Ui/Notification","./Abstract"],function(e,t,i,n){"use strict";var o=function(){};return o.prototype={_getLabel:function(){},_getAjaxActionName:function(){},_ajaxSuccess:function(){},_ajaxSetup:function(){},init:function(){},_initButton:function(){},_toggle:function(){},_updateButton:function(){}},o}),define("WoltLabSuite/Core/Ui/User/Profile/Menu/Item/Ignore",["Core","Language","Ui/Notification","./Abstract"],function(e,t,i,n){"use strict";var o=function(){};return o.prototype={_getLabel:function(){},_getAjaxActionName:function(){},_ajaxSuccess:function(){},_ajaxSetup:function(){},init:function(){},_initButton:function(){},_toggle:function(){},_updateButton:function(){}},o}),function(e){e.matches=e.matches||e.mozMatchesSelector||e.msMatchesSelector||e.oMatchesSelector||e.webkitMatchesSelector,e.closest=e.closest||function(e){for(var t=this;t&&!t.matches(e);)t=t.parentElement;return t}}(Element.prototype),define("closest",function(){}),function(e){function t(){for(;n.length&&"function"==typeof n[0];)n.shift()()}var i=e.require,n=[],o=0;e.orgRequire=i,e.require=function(r,a,s){if(!Array.isArray(r))return i.apply(e,arguments);var l=new Promise(function(e,a){var s=o++;n.push(s),i(r,function(){var i=arguments;n[n.indexOf(s)]=function(){e(i)},t()},function(e){n[n.indexOf(s)]=function(){a(e)},t()})});return a&&(l=l.then(function(t){return a.apply(e,t)})),s&&l.catch(s),l},e.require.config=i.config}(window),define("require.linearExecution",function(){});
\ No newline at end of file