- *
+ /**
+ * Simple SMTP connection testing.
-define(['Ajax', 'Core', 'Language'], function(Ajax, Core, Language) {
- "use strict";
-
- var _buttonRunTest = null;
- var _container = null;
-
- return {
- init: function () {
- var smtpCheckbox = null;
- var methods = elBySelAll('input[name="values[mail_send_method]"]', undefined, (function (radioCheckbox) {
- radioCheckbox.addEventListener('change', this._onChange.bind(this));
-
- if (radioCheckbox.value === 'smtp') smtpCheckbox = radioCheckbox;
- }).bind(this));
-
- // This configuration part is unavailable when running in enterprise mode.
- if (methods.length === 0) {
- return;
- }
-
- Core.triggerEvent(smtpCheckbox, 'change');
- },
-
- _onChange: function (event) {
- var checkbox = event.currentTarget;
-
- if (checkbox.value === 'smtp' && checkbox.checked) {
- if (_container === null) this._initUi(checkbox);
-
- elShow(_container);
- }
- else if (_container !== null) {
- elHide(_container);
- }
- },
-
- _initUi: function (checkbox) {
- var html = '<dt>' + Language.get('wcf.acp.email.smtp.test') + '</dt>';
- html += '<dd>';
- html += '<a href="#" class="button">' + Language.get('wcf.acp.email.smtp.test.run') + '</a>';
- html += '<small>' + Language.get('wcf.acp.email.smtp.test.description') + '</small>';
- html += '</dd>';
-
- _container = elCreate('dl');
- _container.innerHTML = html;
-
- _buttonRunTest = elBySel('a', _container);
- _buttonRunTest.addEventListener(WCF_CLICK_EVENT, this._onClick.bind(this));
-
- var insertAfter = checkbox.closest('dl');
- insertAfter.parentNode.insertBefore(_container, insertAfter.nextSibling);
- },
-
- _onClick: function (event) {
- event.preventDefault();
-
- _buttonRunTest.disabled = true;
- _buttonRunTest.innerHTML = '<span class="icon icon16 fa-spinner"></span> ' + Language.get('wcf.global.loading');
-
- elInnerError(_buttonRunTest, false);
-
- window.setTimeout((function () {
- var startTls = elBySel('input[name="values[mail_smtp_starttls]"]:checked');
-
- Ajax.api(this, {
- parameters: {
- host: elById('mail_smtp_host').value,
- port: elById('mail_smtp_port').value,
- startTls: (startTls) ? startTls.value : '',
- user: elById('mail_smtp_user').value,
- password: elById('mail_smtp_password').value
- }
- });
- }).bind(this), 100);
- },
-
- _ajaxSuccess: function (data) {
- var result = data.returnValues.validationResult;
- if (result === '') {
- this._resetButton(true);
- }
- else {
- this._resetButton(false, result);
- }
- },
-
- _ajaxFailure: function (data) {
- var result = '';
- if (data && data.returnValues && data.returnValues.fieldName) {
- result = Language.get('wcf.acp.email.smtp.test.error.empty.' + data.returnValues.fieldName);
- }
-
- this._resetButton(false, result);
-
- return (result === '');
- },
-
- _resetButton: function (success, errorMessage) {
- _buttonRunTest.disabled = false;
-
- if (success) _buttonRunTest.innerHTML = '<span class="icon icon16 fa-check green"></span> ' + Language.get('wcf.acp.email.smtp.test.run.success');
- else _buttonRunTest.innerHTML = Language.get('wcf.acp.email.smtp.test.run');
-
- if (errorMessage) elInnerError(_buttonRunTest, errorMessage);
- },
-
- _ajaxSetup: function () {
- return {
- data: {
- actionName: 'emailSmtpTest',
- className: 'wcf\\data\\option\\OptionAction'
- },
- silent: true
- };
- }
- };
++ *
+ * @author Alexander Ebert
+ * @copyright 2001-2018 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLabSuite/Core/Acp/Ui/Option/EmailSmtpTest
+ */
+define(['Ajax', 'Core', 'Language'], function (Ajax, Core, Language) {
+ "use strict";
+ var _buttonRunTest = null;
+ var _container = null;
+ return {
+ init: function () {
+ var smtpCheckbox = null;
+ var methods = elBySelAll('input[name="values[mail_send_method]"]', undefined, (function (radioCheckbox) {
+ radioCheckbox.addEventListener('change', this._onChange.bind(this));
+ if (radioCheckbox.value === 'smtp')
+ smtpCheckbox = radioCheckbox;
+ }).bind(this));
+ // This configuration part is unavailable when running in enterprise mode.
+ if (methods.length === 0) {
+ return;
+ }
+ Core.triggerEvent(smtpCheckbox, 'change');
+ },
+ _onChange: function (event) {
+ var checkbox = event.currentTarget;
+ if (checkbox.value === 'smtp' && checkbox.checked) {
+ if (_container === null)
+ this._initUi(checkbox);
+ elShow(_container);
+ }
+ else if (_container !== null) {
+ elHide(_container);
+ }
+ },
+ _initUi: function (checkbox) {
+ var html = '<dt>' + Language.get('wcf.acp.email.smtp.test') + '</dt>';
+ html += '<dd>';
+ html += '<a href="#" class="button">' + Language.get('wcf.acp.email.smtp.test.run') + '</a>';
+ html += '<small>' + Language.get('wcf.acp.email.smtp.test.description') + '</small>';
+ html += '</dd>';
+ _container = elCreate('dl');
+ _container.innerHTML = html;
+ _buttonRunTest = elBySel('a', _container);
+ _buttonRunTest.addEventListener('click', this._onClick.bind(this));
+ var insertAfter = checkbox.closest('dl');
+ insertAfter.parentNode.insertBefore(_container, insertAfter.nextSibling);
+ },
+ _onClick: function (event) {
+ event.preventDefault();
+ _buttonRunTest.disabled = true;
+ _buttonRunTest.innerHTML = '<span class="icon icon16 fa-spinner"></span> ' + Language.get('wcf.global.loading');
+ elInnerError(_buttonRunTest, false);
+ window.setTimeout((function () {
+ var startTls = elBySel('input[name="values[mail_smtp_starttls]"]:checked');
+ Ajax.api(this, {
+ parameters: {
+ host: elById('mail_smtp_host').value,
+ port: elById('mail_smtp_port').value,
+ startTls: (startTls) ? startTls.value : '',
+ user: elById('mail_smtp_user').value,
+ password: elById('mail_smtp_password').value
+ }
+ });
+ }).bind(this), 100);
+ },
+ _ajaxSuccess: function (data) {
+ var result = data.returnValues.validationResult;
+ if (result === '') {
+ this._resetButton(true);
+ }
+ else {
+ this._resetButton(false, result);
+ }
+ },
+ _ajaxFailure: function (data) {
+ var result = '';
+ if (data && data.returnValues && data.returnValues.fieldName) {
+ result = Language.get('wcf.acp.email.smtp.test.error.empty.' + data.returnValues.fieldName);
+ }
+ this._resetButton(false, result);
+ return (result === '');
+ },
+ _resetButton: function (success, errorMessage) {
+ _buttonRunTest.disabled = false;
+ if (success)
+ _buttonRunTest.innerHTML = '<span class="icon icon16 fa-check green"></span> ' + Language.get('wcf.acp.email.smtp.test.run.success');
+ else
+ _buttonRunTest.innerHTML = Language.get('wcf.acp.email.smtp.test.run');
+ if (errorMessage)
+ elInnerError(_buttonRunTest, errorMessage);
+ },
+ _ajaxSetup: function () {
+ return {
+ data: {
+ actionName: 'emailSmtpTest',
+ className: 'wcf\\data\\option\\OptionAction'
+ },
+ silent: true
+ };
+ }
+ };
});
/**
* 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 Ui/Screen (alias)
- * @module WoltLabSuite/Core/Ui/Screen
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module Ui/Screen (alias)
+ * @module WoltLabSuite/Core/Ui/Screen
*/
-define(['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 */
- 'screen-lg-only': '(min-width: 1025px) and (max-width: 1280px)',
- 'screen-lg-down': '(max-width: 1280px)',
- 'screen-xl': '(min-width: 1281px)'
- });
-
- // 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 {
- // Chromium based browsers running on Windows suffer from a bug when
- // used with the responsive mode of the DevTools. Enabling and
- // disabling it will trigger some media queries to report a change
- // even when there isn't really one. This cause errors when invoking
- // "unmatch" handlers that rely on the setup being executed before.
- if (queryObject.callbacksSetup.size) {
- return;
- }
-
- queryObject.callbacksUnmatch.forEach(function(callback) {
- callback();
- });
- }
- }
- };
+define(["require", "exports", "tslib", "../Core", "../Environment"], function (require, exports, tslib_1, Core, Environment) {
+ "use strict";
+ Object.defineProperty(exports, "__esModule", { value: true });
+ exports.setDialogContainer = exports.pageOverlayIsActive = exports.pageOverlayClose = exports.pageOverlayOpen = exports.scrollEnable = exports.scrollDisable = exports.is = exports.remove = exports.on = void 0;
+ Core = tslib_1.__importStar(Core);
+ Environment = tslib_1.__importStar(Environment);
+ const _mql = new Map();
+ let _scrollDisableCounter = 0;
+ let _scrollOffsetFrom;
+ let _scrollTop = 0;
+ let _pageOverlayCounter = 0;
+ const _mqMap = new Map(Object.entries({
+ "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 */,
+ "screen-lg-only": "(min-width: 1025px) and (max-width: 1280px)",
+ "screen-lg-down": "(max-width: 1280px)",
+ "screen-xl": "(min-width: 1281px)",
+ }));
+ // Microsoft Edge rewrites the media queries to whatever it
+ // pleases, causing the input and output query to mismatch
+ const _mqMapEdge = new Map();
+ /**
+ * 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.
+ */
+ function on(query, callbacks) {
+ const uuid = Core.getUuid(), queryObject = _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;
+ }
+ exports.on = on;
+ /**
+ * Removes all listeners identified by their common UUID.
+ */
+ function remove(query, uuid) {
+ const queryObject = _getQueryObject(query);
+ queryObject.callbacksMatch.delete(uuid);
+ queryObject.callbacksUnmatch.delete(uuid);
+ queryObject.callbacksSetup.delete(uuid);
+ }
+ exports.remove = remove;
+ /**
+ * Returns a boolean value if a media query expression currently matches.
+ */
+ function is(query) {
+ return _getQueryObject(query).mql.matches;
+ }
+ exports.is = is;
+ /**
+ * Disables scrolling of body element.
+ */
+ function scrollDisable() {
+ if (_scrollDisableCounter === 0) {
+ _scrollTop = document.body.scrollTop;
+ _scrollOffsetFrom = "body";
+ if (!_scrollTop) {
+ _scrollTop = document.documentElement.scrollTop;
+ _scrollOffsetFrom = "documentElement";
+ }
+ const pageContainer = document.getElementById("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++;
+ }
+ exports.scrollDisable = scrollDisable;
+ /**
+ * Re-enables scrolling of body element.
+ */
+ function scrollEnable() {
+ if (_scrollDisableCounter) {
+ _scrollDisableCounter--;
+ if (_scrollDisableCounter === 0) {
+ document.documentElement.classList.remove("disableScrolling");
+ const pageContainer = document.getElementById("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;
+ }
+ }
+ }
+ }
+ exports.scrollEnable = scrollEnable;
+ /**
+ * Indicates that at least one page overlay is currently open.
+ */
+ function pageOverlayOpen() {
+ if (_pageOverlayCounter === 0) {
+ document.documentElement.classList.add("pageOverlayActive");
+ }
+ _pageOverlayCounter++;
+ }
+ exports.pageOverlayOpen = pageOverlayOpen;
+ /**
+ * Marks one page overlay as closed.
+ */
+ function pageOverlayClose() {
+ if (_pageOverlayCounter) {
+ _pageOverlayCounter--;
+ if (_pageOverlayCounter === 0) {
+ document.documentElement.classList.remove("pageOverlayActive");
+ }
+ }
+ }
+ exports.pageOverlayClose = pageOverlayClose;
+ /**
+ * Returns true if at least one page overlay is currently open.
+ *
+ * @returns {boolean}
+ */
+ function pageOverlayIsActive() {
+ return _pageOverlayCounter > 0;
+ }
+ exports.pageOverlayIsActive = pageOverlayIsActive;
+ /**
+ * @deprecated 5.4 - This method is a noop.
+ */
+ function setDialogContainer(_container) {
+ // Do nothing.
+ }
+ exports.setDialogContainer = setDialogContainer;
+ function _getQueryObject(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);
+ let queryObject = _mql.get(query);
+ if (!queryObject) {
+ queryObject = {
+ callbacksMatch: new Map(),
+ callbacksUnmatch: new Map(),
+ callbacksSetup: new Map(),
+ mql: window.matchMedia(query),
+ };
+ //noinspection JSDeprecatedSymbols
+ queryObject.mql.addListener(_mqlChange);
+ _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.
+ */
+ function _mqlChange(event) {
+ const queryObject = _getQueryObject(event.media);
+ if (event.matches) {
+ if (queryObject.callbacksSetup.size) {
+ queryObject.callbacksSetup.forEach((callback) => {
+ callback();
+ });
+ // discard all setup callbacks after execution
+ queryObject.callbacksSetup = new Map();
+ }
+ else {
+ queryObject.callbacksMatch.forEach((callback) => {
+ callback();
+ });
+ }
+ }
+ else {
++ // Chromium based browsers running on Windows suffer from a bug when
++ // used with the responsive mode of the DevTools. Enabling and
++ // disabling it will trigger some media queries to report a change
++ // even when there isn't really one. This cause errors when invoking
++ // "unmatch" handlers that rely on the setup being executed before.
++ if (queryObject.callbacksSetup.size) {
++ return;
++ }
+ queryObject.callbacksUnmatch.forEach((callback) => {
+ callback();
+ });
+ }
+ }
});
--- /dev/null
++/**
++ * Simple SMTP connection testing.
++ *
++ * @author Alexander Ebert
++ * @copyright 2001-2018 WoltLab GmbH
++ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
++ * @module WoltLabSuite/Core/Acp/Ui/Option/EmailSmtpTest
++ */
+define(['Ajax', 'Core', 'Language'], function(Ajax, Core, Language) {
+ "use strict";
+
+ var _buttonRunTest = null;
+ var _container = null;
+
+ return {
+ init: function () {
+ var smtpCheckbox = null;
+ var methods = elBySelAll('input[name="values[mail_send_method]"]', undefined, (function (radioCheckbox) {
+ radioCheckbox.addEventListener('change', this._onChange.bind(this));
+
+ if (radioCheckbox.value === 'smtp') smtpCheckbox = radioCheckbox;
+ }).bind(this));
+
+ // This configuration part is unavailable when running in enterprise mode.
+ if (methods.length === 0) {
+ return;
+ }
+
+ Core.triggerEvent(smtpCheckbox, 'change');
+ },
+
+ _onChange: function (event) {
+ var checkbox = event.currentTarget;
+
+ if (checkbox.value === 'smtp' && checkbox.checked) {
+ if (_container === null) this._initUi(checkbox);
+
+ elShow(_container);
+ }
+ else if (_container !== null) {
+ elHide(_container);
+ }
+ },
+
+ _initUi: function (checkbox) {
+ var html = '<dt>' + Language.get('wcf.acp.email.smtp.test') + '</dt>';
+ html += '<dd>';
+ html += '<a href="#" class="button">' + Language.get('wcf.acp.email.smtp.test.run') + '</a>';
+ html += '<small>' + Language.get('wcf.acp.email.smtp.test.description') + '</small>';
+ html += '</dd>';
+
+ _container = elCreate('dl');
+ _container.innerHTML = html;
+
+ _buttonRunTest = elBySel('a', _container);
+ _buttonRunTest.addEventListener('click', this._onClick.bind(this));
+
+ var insertAfter = checkbox.closest('dl');
+ insertAfter.parentNode.insertBefore(_container, insertAfter.nextSibling);
+ },
+
+ _onClick: function (event) {
+ event.preventDefault();
+
+ _buttonRunTest.disabled = true;
+ _buttonRunTest.innerHTML = '<span class="icon icon16 fa-spinner"></span> ' + Language.get('wcf.global.loading');
+
+ elInnerError(_buttonRunTest, false);
+
+ window.setTimeout((function () {
+ var startTls = elBySel('input[name="values[mail_smtp_starttls]"]:checked');
+
+ Ajax.api(this, {
+ parameters: {
+ host: elById('mail_smtp_host').value,
+ port: elById('mail_smtp_port').value,
+ startTls: (startTls) ? startTls.value : '',
+ user: elById('mail_smtp_user').value,
+ password: elById('mail_smtp_password').value
+ }
+ });
+ }).bind(this), 100);
+ },
+
+ _ajaxSuccess: function (data) {
+ var result = data.returnValues.validationResult;
+ if (result === '') {
+ this._resetButton(true);
+ }
+ else {
+ this._resetButton(false, result);
+ }
+ },
+
+ _ajaxFailure: function (data) {
+ var result = '';
+ if (data && data.returnValues && data.returnValues.fieldName) {
+ result = Language.get('wcf.acp.email.smtp.test.error.empty.' + data.returnValues.fieldName);
+ }
+
+ this._resetButton(false, result);
+
+ return (result === '');
+ },
+
+ _resetButton: function (success, errorMessage) {
+ _buttonRunTest.disabled = false;
+
+ if (success) _buttonRunTest.innerHTML = '<span class="icon icon16 fa-check green"></span> ' + Language.get('wcf.acp.email.smtp.test.run.success');
+ else _buttonRunTest.innerHTML = Language.get('wcf.acp.email.smtp.test.run');
+
+ if (errorMessage) elInnerError(_buttonRunTest, errorMessage);
+ },
+
+ _ajaxSetup: function () {
+ return {
+ data: {
+ actionName: 'emailSmtpTest',
+ className: 'wcf\\data\\option\\OptionAction'
+ },
+ silent: true
+ };
+ }
+ };
+});
--- /dev/null
+/**
+ * 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 Ui/Screen (alias)
+ * @module WoltLabSuite/Core/Ui/Screen
+ */
+
+import * as Core from "../Core";
+import * as Environment from "../Environment";
+
+const _mql = new Map<string, MediaQueryData>();
+
+let _scrollDisableCounter = 0;
+let _scrollOffsetFrom: string;
+let _scrollTop = 0;
+let _pageOverlayCounter = 0;
+
+const _mqMap = new Map<string, string>(
+ Object.entries({
+ "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 */,
+ "screen-lg-only": "(min-width: 1025px) and (max-width: 1280px)",
+ "screen-lg-down": "(max-width: 1280px)",
+ "screen-xl": "(min-width: 1281px)",
+ }),
+);
+
+// Microsoft Edge rewrites the media queries to whatever it
+// pleases, causing the input and output query to mismatch
+const _mqMapEdge = new Map<string, string>();
+
+/**
+ * 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.
+ */
+export function on(query: string, callbacks: Callbacks): string {
+ const uuid = Core.getUuid(),
+ queryObject = _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.
+ */
+export function remove(query: string, uuid: string): void {
+ const queryObject = _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.
+ */
+export function is(query: string): boolean {
+ return _getQueryObject(query).mql.matches;
+}
+
+/**
+ * Disables scrolling of body element.
+ */
+export function scrollDisable(): void {
+ if (_scrollDisableCounter === 0) {
+ _scrollTop = document.body.scrollTop;
+ _scrollOffsetFrom = "body";
+ if (!_scrollTop) {
+ _scrollTop = document.documentElement.scrollTop;
+ _scrollOffsetFrom = "documentElement";
+ }
+
+ const pageContainer = document.getElementById("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.
+ */
+export function scrollEnable(): void {
+ if (_scrollDisableCounter) {
+ _scrollDisableCounter--;
+
+ if (_scrollDisableCounter === 0) {
+ document.documentElement.classList.remove("disableScrolling");
+
+ const pageContainer = document.getElementById("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.
+ */
+export function pageOverlayOpen(): void {
+ if (_pageOverlayCounter === 0) {
+ document.documentElement.classList.add("pageOverlayActive");
+ }
+
+ _pageOverlayCounter++;
+}
+
+/**
+ * Marks one page overlay as closed.
+ */
+export function pageOverlayClose(): void {
+ if (_pageOverlayCounter) {
+ _pageOverlayCounter--;
+
+ if (_pageOverlayCounter === 0) {
+ document.documentElement.classList.remove("pageOverlayActive");
+ }
+ }
+}
+
+/**
+ * Returns true if at least one page overlay is currently open.
+ *
+ * @returns {boolean}
+ */
+export function pageOverlayIsActive(): boolean {
+ return _pageOverlayCounter > 0;
+}
+
+/**
+ * @deprecated 5.4 - This method is a noop.
+ */
+export function setDialogContainer(_container: Element): void {
+ // Do nothing.
+}
+
+function _getQueryObject(query: string): MediaQueryData {
+ if (typeof (query as any) !== "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) as string;
+
+ let queryObject = _mql.get(query);
+ if (!queryObject) {
+ queryObject = {
+ callbacksMatch: new Map<string, Callback>(),
+ callbacksUnmatch: new Map<string, Callback>(),
+ callbacksSetup: new Map<string, Callback>(),
+ mql: window.matchMedia(query),
+ };
+ //noinspection JSDeprecatedSymbols
+ queryObject.mql.addListener(_mqlChange);
+
+ _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.
+ */
+function _mqlChange(event: MediaQueryListEvent): void {
+ const queryObject = _getQueryObject(event.media);
+ if (event.matches) {
+ if (queryObject.callbacksSetup.size) {
+ queryObject.callbacksSetup.forEach((callback) => {
+ callback();
+ });
+
+ // discard all setup callbacks after execution
+ queryObject.callbacksSetup = new Map<string, Callback>();
+ } else {
+ queryObject.callbacksMatch.forEach((callback) => {
+ callback();
+ });
+ }
+ } else {
++ // Chromium based browsers running on Windows suffer from a bug when
++ // used with the responsive mode of the DevTools. Enabling and
++ // disabling it will trigger some media queries to report a change
++ // even when there isn't really one. This cause errors when invoking
++ // "unmatch" handlers that rely on the setup being executed before.
++ if (queryObject.callbacksSetup.size) {
++ return;
++ }
++
+ queryObject.callbacksUnmatch.forEach((callback) => {
+ callback();
+ });
+ }
+}
+
+type Callback = () => void;
+
+interface Callbacks {
+ match: Callback;
+ setup: Callback;
+ unmatch: Callback;
+}
+
+interface MediaQueryData {
+ callbacksMatch: Map<string, Callback>;
+ callbacksSetup: Map<string, Callback>;
+ callbacksUnmatch: Map<string, Callback>;
+ mql: MediaQueryList;
+}