From: Tim Düsterhus Date: Mon, 23 Nov 2020 10:36:46 +0000 (+0100) Subject: Merge branch '5.3' X-Git-Tag: 5.4.0_Alpha_1~592 X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=ca61e49e66275ab3d016ab87cdb0ee021cd7389d;p=GitHub%2FWoltLab%2FWCF.git Merge branch '5.3' - Dropped update_com.woltlab.wcf_5.3_orphanedComments.php - Replaced ts/WoltLabSuite/Core/Acp/Ui/Option/EmailSmtpTest.js with the file from 5.3, replacing WCF_CLICK_EVENT with 'click' and regenerated the compiled JavaScript. - Manually applied 4ac5f76b4ee5804919a832729a7ab384ea9d9a4d to the already converted WoltLabSuite/Core/Ui/Screen.ts --- ca61e49e66275ab3d016ab87cdb0ee021cd7389d diff --cc wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Option/EmailSmtpTest.js index ea1bdd9038,3b2d8d6c72..e006a1c204 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Option/EmailSmtpTest.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Option/EmailSmtpTest.js @@@ -1,97 -1,125 +1,105 @@@ + /** + * Simple SMTP connection testing. - * ++ * + * @author Alexander Ebert + * @copyright 2001-2018 WoltLab GmbH + * @license GNU Lesser General Public License + * @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 = '
' + Language.get('wcf.acp.email.smtp.test') + '
'; - html += '
'; - html += '' + Language.get('wcf.acp.email.smtp.test.run') + ''; - html += '' + Language.get('wcf.acp.email.smtp.test.description') + ''; - html += '
'; - - _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 = ' ' + 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 = ' ' + 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 - }; - } - }; +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 = '
' + Language.get('wcf.acp.email.smtp.test') + '
'; + html += '
'; + html += '' + Language.get('wcf.acp.email.smtp.test.run') + ''; + html += '' + Language.get('wcf.acp.email.smtp.test.description') + ''; + html += '
'; + _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 = ' ' + 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 = ' ' + 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 + }; + } + }; }); diff --cc wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Screen.js index 7bf7547bce,307d315e63..26cae7dd84 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Screen.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Screen.js @@@ -1,223 -1,280 +1,231 @@@ /** * Provides consistent support for media queries and body scrolling. - * - * @author Alexander Ebert - * @copyright 2001-2019 WoltLab GmbH - * @license GNU Lesser General Public License - * @module Ui/Screen (alias) - * @module WoltLabSuite/Core/Ui/Screen + * + * @author Alexander Ebert + * @copyright 2001-2019 WoltLab GmbH + * @license GNU Lesser General Public License + * @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(); + }); + } + } }); diff --cc wcfsetup/install/files/ts/WoltLabSuite/Core/Acp/Ui/Option/EmailSmtpTest.js index fc887ebc1a,0000000000..cc59cfcd8c mode 100644,000000..100644 --- a/wcfsetup/install/files/ts/WoltLabSuite/Core/Acp/Ui/Option/EmailSmtpTest.js +++ b/wcfsetup/install/files/ts/WoltLabSuite/Core/Acp/Ui/Option/EmailSmtpTest.js @@@ -1,117 -1,0 +1,125 @@@ ++/** ++ * Simple SMTP connection testing. ++ * ++ * @author Alexander Ebert ++ * @copyright 2001-2018 WoltLab GmbH ++ * @license GNU Lesser General Public License ++ * @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 = '
' + Language.get('wcf.acp.email.smtp.test') + '
'; + html += '
'; + html += '' + Language.get('wcf.acp.email.smtp.test.run') + ''; + html += '' + Language.get('wcf.acp.email.smtp.test.description') + ''; + html += '
'; + + _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 = ' ' + 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 = ' ' + 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 + }; + } + }; +}); diff --cc wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Screen.ts index 0adedde0bd,0000000000..f2051a15bb mode 100644,000000..100644 --- a/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Screen.ts +++ b/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Screen.ts @@@ -1,257 -1,0 +1,266 @@@ +/** + * Provides consistent support for media queries and body scrolling. + * + * @author Alexander Ebert + * @copyright 2001-2019 WoltLab GmbH + * @license GNU Lesser General Public License + * @module Ui/Screen (alias) + * @module WoltLabSuite/Core/Ui/Screen + */ + +import * as Core from "../Core"; +import * as Environment from "../Environment"; + +const _mql = new Map(); + +let _scrollDisableCounter = 0; +let _scrollOffsetFrom: string; +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. + */ +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(), + 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: 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(); + } 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; + callbacksSetup: Map; + callbacksUnmatch: Map; + mql: MediaQueryList; +}