From: Alexander Ebert Date: Fri, 4 Dec 2020 14:48:35 +0000 (+0100) Subject: Convert `Acp/Ui/Style/Editor` to TypeScript X-Git-Tag: 5.4.0_Alpha_1~541^2 X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=516c05afd75df135200ac9b37320be4aec950eb9;p=GitHub%2FWoltLab%2FWCF.git Convert `Acp/Ui/Style/Editor` to TypeScript --- diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Style/Editor.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Style/Editor.js index af8129e18b..c66eb5cd0c 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Style/Editor.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Style/Editor.js @@ -1,275 +1,295 @@ /** * Provides the style editor. * - * @author Alexander Ebert - * @copyright 2001-2019 WoltLab GmbH - * @license GNU Lesser General Public License - * @module WoltLabSuite/Core/Acp/Ui/Style/Editor + * @author Alexander Ebert + * @copyright 2001-2019 WoltLab GmbH + * @license GNU Lesser General Public License + * @module WoltLabSuite/Core/Acp/Ui/Style/Editor */ -define(['Ajax', 'Core', 'Dictionary', 'Dom/Util', 'EventHandler', 'Ui/Screen'], function (Ajax, Core, Dictionary, DomUtil, EventHandler, UiScreen) { +define(["require", "exports", "tslib", "../../../Ajax", "../../../Core", "../../../Dom/Util", "../../../Event/Handler", "../../../Ui/Screen"], function (require, exports, tslib_1, Ajax, Core, Util_1, EventHandler, UiScreen) { "use strict"; - var _stylePreviewRegions = new Dictionary(); - var _stylePreviewRegionMarker = null; - var _stylePreviewWindow = elById('spWindow'); - var _isVisible = true; - var _isSmartphone = false; - var _updateRegionMarker = null; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.showVisualEditor = exports.hideVisualEditor = exports.setup = void 0; + Ajax = tslib_1.__importStar(Ajax); + Core = tslib_1.__importStar(Core); + Util_1 = tslib_1.__importDefault(Util_1); + EventHandler = tslib_1.__importStar(EventHandler); + UiScreen = tslib_1.__importStar(UiScreen); + const _stylePreviewRegions = new Map(); + let _stylePreviewRegionMarker; + const _stylePreviewWindow = document.getElementById("spWindow"); + let _isVisible = true; + let _isSmartphone = false; + let _updateRegionMarker; /** - * @module WoltLabSuite/Core/Acp/Ui/Style/Editor + * Handles the switch between static and fluid layout. */ - return { - /** - * Sets up dynamic style options. - */ - setup: function (options) { - this._handleLayoutWidth(); - this._handleScss(options.isTainted); - if (!options.isTainted) { - this._handleProtection(options.styleId); + function handleLayoutWidth() { + const useFluidLayout = document.getElementById("useFluidLayout"); + const fluidLayoutMinWidth = document.getElementById("fluidLayoutMinWidth"); + const fluidLayoutMaxWidth = document.getElementById("fluidLayoutMaxWidth"); + const fixedLayoutVariables = document.getElementById("fixedLayoutVariables"); + function change() { + if (useFluidLayout.checked) { + Util_1.default.show(fluidLayoutMinWidth); + Util_1.default.show(fluidLayoutMaxWidth); + Util_1.default.hide(fixedLayoutVariables); } - this._initVisualEditor(options.styleRuleMap); - UiScreen.on('screen-sm-down', { - match: this.hideVisualEditor.bind(this), - unmatch: this.showVisualEditor.bind(this), - setup: this.hideVisualEditor.bind(this) - }); - var callbackRegionMarker = function () { - if (_isVisible) - _updateRegionMarker(); - }; - window.addEventListener('resize', callbackRegionMarker); - EventHandler.add('com.woltlab.wcf.AcpMenu', 'resize', callbackRegionMarker); - EventHandler.add('com.woltlab.wcf.simpleTabMenu_styleTabMenuContainer', 'select', function (data) { - _isVisible = (data.activeName === 'colors'); - callbackRegionMarker(); - }); - }, - /** - * Handles the switch between static and fluid layout. - */ - _handleLayoutWidth: function () { - var useFluidLayout = elById('useFluidLayout'); - var fluidLayoutMinWidth = elById('fluidLayoutMinWidth'); - var fluidLayoutMaxWidth = elById('fluidLayoutMaxWidth'); - var fixedLayoutVariables = elById('fixedLayoutVariables'); - function change() { - var checked = useFluidLayout.checked; - fluidLayoutMinWidth.style[(checked ? 'remove' : 'set') + 'Property']('display', 'none'); - fluidLayoutMaxWidth.style[(checked ? 'remove' : 'set') + 'Property']('display', 'none'); - fixedLayoutVariables.style[(checked ? 'set' : 'remove') + 'Property']('display', 'none'); + else { + Util_1.default.hide(fluidLayoutMinWidth); + Util_1.default.hide(fluidLayoutMaxWidth); + Util_1.default.show(fixedLayoutVariables); } - useFluidLayout.addEventListener('change', change); - change(); - }, - /** - * Handles SCSS input fields. - * - * @param {boolean} isTainted false if style is in protected mode - */ - _handleScss: function (isTainted) { - var individualScss = elById('individualScss'); - var overrideScss = elById('overrideScss'); - if (isTainted) { - EventHandler.add('com.woltlab.wcf.simpleTabMenu_styleTabMenuContainer', 'select', function (data) { + } + useFluidLayout.addEventListener("change", change); + change(); + } + /** + * Handles SCSS input fields. + */ + function handleScss(isTainted) { + const individualScss = document.getElementById("individualScss"); + const overrideScss = document.getElementById("overrideScss"); + if (isTainted) { + EventHandler.add("com.woltlab.wcf.simpleTabMenu_styleTabMenuContainer", "select", () => { + individualScss.codemirror.refresh(); + overrideScss.codemirror.refresh(); + }); + } + else { + EventHandler.add("com.woltlab.wcf.simpleTabMenu_advanced", "select", (data) => { + if (data.activeName === "advanced-custom") { + document.getElementById("individualScssCustom").codemirror.refresh(); + document.getElementById("overrideScssCustom").codemirror.refresh(); + } + else if (data.activeName === "advanced-original") { individualScss.codemirror.refresh(); overrideScss.codemirror.refresh(); - }); - } - else { - EventHandler.add('com.woltlab.wcf.simpleTabMenu_advanced', 'select', function (data) { - if (data.activeName === 'advanced-custom') { - elById('individualScssCustom').codemirror.refresh(); - elById('overrideScssCustom').codemirror.refresh(); - } - else if (data.activeName === 'advanced-original') { - individualScss.codemirror.refresh(); - overrideScss.codemirror.refresh(); - } - }); - } - }, - _handleProtection: function (styleId) { - var button = elById('styleDisableProtectionSubmit'); - var checkbox = elById('styleDisableProtectionConfirm'); - checkbox.addEventListener('change', function () { - button.disabled = !checkbox.checked; + } }); - button.addEventListener('click', function () { - Ajax.apiOnce({ - data: { - actionName: 'markAsTainted', - className: 'wcf\\data\\style\\StyleAction', - objectIDs: [styleId] - }, - success: function () { - window.location.reload(); - } - }); + } + } + function handleProtection(styleId) { + const button = document.getElementById("styleDisableProtectionSubmit"); + const checkbox = document.getElementById("styleDisableProtectionConfirm"); + checkbox.addEventListener("change", () => { + button.disabled = !checkbox.checked; + }); + button.addEventListener("click", () => { + Ajax.apiOnce({ + data: { + actionName: "markAsTainted", + className: "wcf\\data\\style\\StyleAction", + objectIDs: [styleId], + }, + success: () => { + window.location.reload(); + }, }); - }, - _initVisualEditor: function (styleRuleMap) { - elBySelAll('[data-region]', _stylePreviewWindow, function (region) { - _stylePreviewRegions.set(elData(region, 'region'), region); + }); + } + function initVisualEditor(styleRuleMap) { + _stylePreviewWindow.querySelectorAll("[data-region]").forEach((region) => { + _stylePreviewRegions.set(region.dataset.region, region); + }); + _stylePreviewRegionMarker = document.createElement("div"); + _stylePreviewRegionMarker.id = "stylePreviewRegionMarker"; + _stylePreviewRegionMarker.innerHTML = '
'; + Util_1.default.hide(_stylePreviewRegionMarker); + document.getElementById("colors").appendChild(_stylePreviewRegionMarker); + const container = document.getElementById("spSidebar"); + const select = document.getElementById("spCategories"); + let lastValue = select.value; + _updateRegionMarker = () => { + if (_isSmartphone) { + return; + } + if (lastValue === "none") { + Util_1.default.hide(_stylePreviewRegionMarker); + return; + } + const region = _stylePreviewRegions.get(lastValue); + const rect = region.getBoundingClientRect(); + let top = rect.top + (window.scrollY || window.pageYOffset); + Util_1.default.setStyles(_stylePreviewRegionMarker, { + height: `${region.clientHeight + 20}px`, + left: `${rect.left + document.body.scrollLeft - 10}px`, + top: `${top - 10}px`, + width: `${region.clientWidth + 20}px`, }); - _stylePreviewRegionMarker = elCreate('div'); - _stylePreviewRegionMarker.id = 'stylePreviewRegionMarker'; - _stylePreviewRegionMarker.innerHTML = '
'; - elHide(_stylePreviewRegionMarker); - elById('colors').appendChild(_stylePreviewRegionMarker); - var container = elById('spSidebar'); - var select = elById('spCategories'); - var lastValue = select.value; - _updateRegionMarker = function () { - if (_isSmartphone) { - return; - } - if (lastValue === 'none') { - elHide(_stylePreviewRegionMarker); - return; - } - var region = _stylePreviewRegions.get(lastValue); - var rect = region.getBoundingClientRect(); - var top = rect.top + (window.scrollY || window.pageYOffset); - DomUtil.setStyles(_stylePreviewRegionMarker, { - height: (region.clientHeight + 20) + 'px', - left: (rect.left + document.body.scrollLeft - 10) + 'px', - top: (top - 10) + 'px', - width: (region.clientWidth + 20) + 'px' - }); - elShow(_stylePreviewRegionMarker); - top = DomUtil.offset(region).top; - // `+ 80` = account for sticky header + selection markers (20px) - var firstVisiblePixel = (window.pageYOffset || window.scrollY) + 80; - if (firstVisiblePixel > top) { - window.scrollTo(0, Math.max(top - 80, 0)); + Util_1.default.show(_stylePreviewRegionMarker); + top = Util_1.default.offset(region).top; + // `+ 80` = account for sticky header + selection markers (20px) + const firstVisiblePixel = (window.pageYOffset || window.scrollY) + 80; + if (firstVisiblePixel > top) { + window.scrollTo(0, Math.max(top - 80, 0)); + } + else { + const lastVisiblePixel = window.innerHeight + (window.pageYOffset || window.scrollY); + if (lastVisiblePixel < top) { + window.scrollTo(0, top); } else { - var lastVisiblePixel = window.innerHeight + (window.pageYOffset || window.scrollY); - if (lastVisiblePixel < top) { - window.scrollTo(0, top); - } - else { - var bottom = top + region.offsetHeight + 20; - if (lastVisiblePixel < bottom) { - window.scrollBy(0, bottom - top); - } + const bottom = top + region.offsetHeight + 20; + if (lastVisiblePixel < bottom) { + window.scrollBy(0, bottom - top); } } - }; - var apiVersions = elBySel('.spSidebarBox[data-category="apiVersion"]', container); - var element; - var callbackChange = function () { - element = elBySel('.spSidebarBox[data-category="' + lastValue + '"]', container); - elHide(element); - lastValue = select.value; - element = elBySel('.spSidebarBox[data-category="' + lastValue + '"]', container); - elShow(element); - var showCompatibilityNotice = elBySel('.spApiVersion', element) !== null; - window[showCompatibilityNotice ? 'elShow' : 'elHide'](apiVersions); - // set region marker - _updateRegionMarker(); - }; - select.addEventListener('change', callbackChange); - // apply CSS rules - var style = elCreate('style'); - style.appendChild(document.createTextNode('')); - elData(style, 'created-by', 'WoltLab/Acp/Ui/Style/Editor'); - document.head.appendChild(style); - function updateCSSRule(identifier, value) { - if (styleRuleMap[identifier] === undefined) { - return; - } - var rule = styleRuleMap[identifier].replace(/VALUE/g, value + ' !important'); - if (!rule) { - return; - } - var rules = []; - if (rule.indexOf('__COMBO_RULE__')) { - rules = rule.split('__COMBO_RULE__'); - } - else { - rules = [rule]; + } + }; + const apiVersions = container.querySelector('.spSidebarBox[data-category="apiVersion"]'); + const callbackChange = () => { + let element = container.querySelector(`.spSidebarBox[data-category="${lastValue}"]`); + Util_1.default.hide(element); + lastValue = select.value; + element = container.querySelector(`.spSidebarBox[data-category="${lastValue}"]`); + Util_1.default.show(element); + const showCompatibilityNotice = element.querySelector(".spApiVersion") !== null; + if (showCompatibilityNotice) { + Util_1.default.show(apiVersions); + } + else { + Util_1.default.hide(apiVersions); + } + // set region marker + _updateRegionMarker(); + }; + select.addEventListener("change", callbackChange); + // apply CSS rules + const style = document.createElement("style"); + style.appendChild(document.createTextNode("")); + style.dataset.createdBy = "WoltLab/Acp/Ui/Style/Editor"; + document.head.appendChild(style); + function updateCSSRule(identifier, value) { + if (styleRuleMap[identifier] === undefined) { + return; + } + const rule = styleRuleMap[identifier].replace(/VALUE/g, value + " !important"); + if (!rule) { + return; + } + let rules; + if (rule.indexOf("__COMBO_RULE__")) { + rules = rule.split("__COMBO_RULE__"); + } + else { + rules = [rule]; + } + rules.forEach((rule) => { + try { + style.sheet.insertRule(rule, style.sheet.cssRules.length); } - for (var i = 0, length = rules.length; i < length; i++) { - try { - style.sheet.insertRule(rules[i], style.sheet.cssRules.length); - } - catch (e) { - // ignore errors for unknown placeholder selectors - if (!/[a-z]+\-placeholder/.test(rules[i])) { - console.debug(e.message); - } + catch (e) { + // ignore errors for unknown placeholder selectors + if (!/[a-z]+-placeholder/.test(rule)) { + console.debug(e.message); } } - } - var elements = elByClass('styleVariableColor', elById('spVariablesWrapper')); - [].forEach.call(elements, function (colorField) { - var variableName = elData(colorField, 'store').replace(/_value$/, ''); - var observer = new MutationObserver(function (mutations) { - mutations.forEach(function (mutation) { - if (mutation.attributeName === 'style') { - updateCSSRule(variableName, colorField.style.getPropertyValue('background-color')); - } - }); - }); - observer.observe(colorField, { - attributes: true - }); - updateCSSRule(variableName, colorField.style.getPropertyValue('background-color')); - }); - // category selection by clicking on the area - var buttonToggleColorPalette = elBySel('.jsButtonToggleColorPalette'); - var buttonSelectCategoryByClick = elBySel('.jsButtonSelectCategoryByClick'); - var toggleSelectionMode = function () { - buttonSelectCategoryByClick.classList.toggle('active'); - buttonToggleColorPalette.classList.toggle('disabled'); - _stylePreviewWindow.classList.toggle('spShowRegions'); - _stylePreviewRegionMarker.classList.toggle('forceHide'); - select.disabled = !select.disabled; - }; - buttonSelectCategoryByClick.addEventListener('click', function (event) { - event.preventDefault(); - toggleSelectionMode(); }); - elBySelAll('[data-region]', _stylePreviewWindow, function (region) { - region.addEventListener('click', function (event) { - if (!_stylePreviewWindow.classList.contains('spShowRegions')) { - return; + } + const wrapper = document.getElementById("spVariablesWrapper"); + wrapper.querySelectorAll(".styleVariableColor").forEach((colorField) => { + const variableName = colorField.dataset.store.replace(/_value$/, ""); + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.attributeName === "style") { + updateCSSRule(variableName, colorField.style.getPropertyValue("background-color")); } - event.preventDefault(); - event.stopPropagation(); - toggleSelectionMode(); - select.value = elData(region, 'region'); - // Programmatically trigger the change event handler, rather than dispatching an event, - // because Firefox fails to execute the event if it has previously been disabled. - // See https://bugzilla.mozilla.org/show_bug.cgi?id=1426856 - callbackChange(); }); }); - // toggle view - var spSelectCategory = elById('spSelectCategory'); - buttonToggleColorPalette.addEventListener('click', function (event) { + observer.observe(colorField, { + attributes: true, + }); + updateCSSRule(variableName, colorField.style.getPropertyValue("background-color")); + }); + // category selection by clicking on the area + const buttonToggleColorPalette = document.querySelector(".jsButtonToggleColorPalette"); + const buttonSelectCategoryByClick = document.querySelector(".jsButtonSelectCategoryByClick"); + function toggleSelectionMode() { + buttonSelectCategoryByClick.classList.toggle("active"); + buttonToggleColorPalette.classList.toggle("disabled"); + _stylePreviewWindow.classList.toggle("spShowRegions"); + _stylePreviewRegionMarker.classList.toggle("forceHide"); + select.disabled = !select.disabled; + } + buttonSelectCategoryByClick.addEventListener("click", (event) => { + event.preventDefault(); + toggleSelectionMode(); + }); + _stylePreviewWindow.querySelectorAll("[data-region]").forEach((region) => { + region.addEventListener("click", (event) => { + if (!_stylePreviewWindow.classList.contains("spShowRegions")) { + return; + } event.preventDefault(); - buttonSelectCategoryByClick.classList.toggle('disabled'); - elToggle(spSelectCategory); - buttonToggleColorPalette.classList.toggle('active'); - _stylePreviewWindow.classList.toggle('spColorPalette'); - _stylePreviewRegionMarker.classList.toggle('forceHide'); - select.disabled = !select.disabled; + event.stopPropagation(); + toggleSelectionMode(); + select.value = region.dataset.region; + // Programmatically trigger the change event handler, rather than dispatching an event, + // because Firefox fails to execute the event if it has previously been disabled. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=1426856 + callbackChange(); }); - }, - hideVisualEditor: function () { - elHide(_stylePreviewWindow); - elById('spVariablesWrapper').style.removeProperty('transform'); - elHide(elById('stylePreviewRegionMarker')); - _isSmartphone = true; - }, - showVisualEditor: function () { - elShow(_stylePreviewWindow); - window.setTimeout(function () { - Core.triggerEvent(elById('spCategories'), 'change'); - }, 100); - _isSmartphone = false; + }); + // toggle view + const spSelectCategory = document.getElementById("spSelectCategory"); + buttonToggleColorPalette.addEventListener("click", (event) => { + event.preventDefault(); + buttonSelectCategoryByClick.classList.toggle("disabled"); + Util_1.default.toggle(spSelectCategory); + buttonToggleColorPalette.classList.toggle("active"); + _stylePreviewWindow.classList.toggle("spColorPalette"); + _stylePreviewRegionMarker.classList.toggle("forceHide"); + select.disabled = !select.disabled; + }); + } + /** + * Sets up dynamic style options. + */ + function setup(options) { + handleLayoutWidth(); + handleScss(options.isTainted); + if (!options.isTainted) { + handleProtection(options.styleId); + } + initVisualEditor(options.styleRuleMap); + UiScreen.on("screen-sm-down", { + match() { + hideVisualEditor(); + }, + unmatch() { + showVisualEditor(); + }, + setup() { + hideVisualEditor(); + }, + }); + function callbackRegionMarker() { + if (_isVisible) { + _updateRegionMarker(); + } } - }; + window.addEventListener("resize", callbackRegionMarker); + EventHandler.add("com.woltlab.wcf.AcpMenu", "resize", callbackRegionMarker); + EventHandler.add("com.woltlab.wcf.simpleTabMenu_styleTabMenuContainer", "select", function (data) { + _isVisible = data.activeName === "colors"; + callbackRegionMarker(); + }); + } + exports.setup = setup; + function hideVisualEditor() { + Util_1.default.hide(_stylePreviewWindow); + document.getElementById("spVariablesWrapper").style.removeProperty("transform"); + Util_1.default.hide(document.getElementById("stylePreviewRegionMarker")); + _isSmartphone = true; + } + exports.hideVisualEditor = hideVisualEditor; + function showVisualEditor() { + Util_1.default.show(_stylePreviewWindow); + window.setTimeout(() => { + Core.triggerEvent(document.getElementById("spCategories"), "change"); + }, 100); + _isSmartphone = false; + } + exports.showVisualEditor = showVisualEditor; }); diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Dom/Util.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Dom/Util.js index fed4dc3d1c..a98d5d15b5 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Dom/Util.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Dom/Util.js @@ -352,6 +352,17 @@ define(["require", "exports", "tslib", "../StringUtil"], function (require, expo isHidden(element) { return element.style.getPropertyValue("display") === "none"; }, + /** + * Shorthand function to toggle the element visibility using either `hide()` or `show()`. + */ + toggle(element) { + if (this.isHidden(element)) { + this.show(element); + } + else { + this.hide(element); + } + }, /** * Displays or removes an error message below the provided element. */ diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Acp/Ui/Style/Editor.js b/wcfsetup/install/files/ts/WoltLabSuite/Core/Acp/Ui/Style/Editor.js deleted file mode 100644 index fbcdcdd02b..0000000000 --- a/wcfsetup/install/files/ts/WoltLabSuite/Core/Acp/Ui/Style/Editor.js +++ /dev/null @@ -1,329 +0,0 @@ -/** - * Provides the style editor. - * - * @author Alexander Ebert - * @copyright 2001-2019 WoltLab GmbH - * @license GNU Lesser General Public License - * @module WoltLabSuite/Core/Acp/Ui/Style/Editor - */ -define(['Ajax', 'Core', 'Dictionary', 'Dom/Util', 'EventHandler', 'Ui/Screen'], function(Ajax, Core, Dictionary, DomUtil, EventHandler, UiScreen) { - "use strict"; - - var _stylePreviewRegions = new Dictionary(); - var _stylePreviewRegionMarker = null; - var _stylePreviewWindow = elById('spWindow'); - - var _isVisible = true; - var _isSmartphone = false; - var _updateRegionMarker = null; - - /** - * @module WoltLabSuite/Core/Acp/Ui/Style/Editor - */ - return { - /** - * Sets up dynamic style options. - */ - setup: function(options) { - this._handleLayoutWidth(); - this._handleScss(options.isTainted); - - if (!options.isTainted) { - this._handleProtection(options.styleId); - } - - this._initVisualEditor(options.styleRuleMap); - - UiScreen.on('screen-sm-down', { - match: this.hideVisualEditor.bind(this), - unmatch: this.showVisualEditor.bind(this), - setup: this.hideVisualEditor.bind(this) - }); - - var callbackRegionMarker = function () { - if (_isVisible) _updateRegionMarker(); - }; - window.addEventListener('resize', callbackRegionMarker); - EventHandler.add('com.woltlab.wcf.AcpMenu', 'resize', callbackRegionMarker); - EventHandler.add('com.woltlab.wcf.simpleTabMenu_styleTabMenuContainer', 'select', function (data) { - _isVisible = (data.activeName === 'colors'); - callbackRegionMarker(); - }); - }, - - /** - * Handles the switch between static and fluid layout. - */ - _handleLayoutWidth: function() { - var useFluidLayout = elById('useFluidLayout'); - var fluidLayoutMinWidth = elById('fluidLayoutMinWidth'); - var fluidLayoutMaxWidth = elById('fluidLayoutMaxWidth'); - var fixedLayoutVariables = elById('fixedLayoutVariables'); - - function change() { - var checked = useFluidLayout.checked; - - fluidLayoutMinWidth.style[(checked ? 'remove' : 'set') + 'Property']('display', 'none'); - fluidLayoutMaxWidth.style[(checked ? 'remove' : 'set') + 'Property']('display', 'none'); - fixedLayoutVariables.style[(checked ? 'set' : 'remove') + 'Property']('display', 'none'); - } - - useFluidLayout.addEventListener('change', change); - - change(); - }, - - /** - * Handles SCSS input fields. - * - * @param {boolean} isTainted false if style is in protected mode - */ - _handleScss: function(isTainted) { - var individualScss = elById('individualScss'); - var overrideScss = elById('overrideScss'); - - if (isTainted) { - EventHandler.add('com.woltlab.wcf.simpleTabMenu_styleTabMenuContainer', 'select', function(data) { - individualScss.codemirror.refresh(); - overrideScss.codemirror.refresh(); - }); - } - else { - EventHandler.add('com.woltlab.wcf.simpleTabMenu_advanced', 'select', function(data) { - if (data.activeName === 'advanced-custom') { - elById('individualScssCustom').codemirror.refresh(); - elById('overrideScssCustom').codemirror.refresh(); - } - else if (data.activeName === 'advanced-original') { - individualScss.codemirror.refresh(); - overrideScss.codemirror.refresh(); - } - }); - } - }, - - _handleProtection: function(styleId) { - var button = elById('styleDisableProtectionSubmit'); - var checkbox = elById('styleDisableProtectionConfirm'); - - checkbox.addEventListener('change', function() { - button.disabled = !checkbox.checked; - }); - - button.addEventListener('click', function() { - Ajax.apiOnce({ - data: { - actionName: 'markAsTainted', - className: 'wcf\\data\\style\\StyleAction', - objectIDs: [styleId] - }, - success: function() { - window.location.reload(); - } - }); - }); - }, - - _initVisualEditor: function(styleRuleMap) { - elBySelAll('[data-region]', _stylePreviewWindow, function(region) { - _stylePreviewRegions.set(elData(region, 'region'), region); - }); - - _stylePreviewRegionMarker = elCreate('div'); - _stylePreviewRegionMarker.id = 'stylePreviewRegionMarker'; - _stylePreviewRegionMarker.innerHTML = '
'; - elHide(_stylePreviewRegionMarker); - elById('colors').appendChild(_stylePreviewRegionMarker); - - var container = elById('spSidebar'); - var select = elById('spCategories'); - var lastValue = select.value; - - _updateRegionMarker = function() { - if (_isSmartphone) { - return; - } - - if (lastValue === 'none') { - elHide(_stylePreviewRegionMarker); - return; - } - - var region = _stylePreviewRegions.get(lastValue); - var rect = region.getBoundingClientRect(); - - var top = rect.top + (window.scrollY || window.pageYOffset); - - DomUtil.setStyles(_stylePreviewRegionMarker, { - height: (region.clientHeight + 20) + 'px', - left: (rect.left + document.body.scrollLeft - 10) + 'px', - top: (top - 10) + 'px', - width: (region.clientWidth + 20) + 'px' - }); - - elShow(_stylePreviewRegionMarker); - - top = DomUtil.offset(region).top; - // `+ 80` = account for sticky header + selection markers (20px) - var firstVisiblePixel = (window.pageYOffset || window.scrollY) + 80; - if (firstVisiblePixel > top) { - window.scrollTo(0, Math.max(top - 80, 0)); - } - else { - var lastVisiblePixel = window.innerHeight + (window.pageYOffset || window.scrollY); - if (lastVisiblePixel < top) { - window.scrollTo(0, top); - } - else { - var bottom = top + region.offsetHeight + 20; - if (lastVisiblePixel < bottom) { - window.scrollBy(0, bottom - top); - } - } - } - }; - - var apiVersions = elBySel('.spSidebarBox[data-category="apiVersion"]', container); - var element; - var callbackChange = function() { - element = elBySel('.spSidebarBox[data-category="' + lastValue + '"]', container); - elHide(element); - - lastValue = select.value; - element = elBySel('.spSidebarBox[data-category="' + lastValue + '"]', container); - elShow(element); - - var showCompatibilityNotice = elBySel('.spApiVersion', element) !== null; - window[showCompatibilityNotice ? 'elShow' : 'elHide'](apiVersions); - - // set region marker - _updateRegionMarker(); - }; - select.addEventListener('change', callbackChange); - - // apply CSS rules - var style = elCreate('style'); - style.appendChild(document.createTextNode('')); - elData(style, 'created-by', 'WoltLab/Acp/Ui/Style/Editor'); - document.head.appendChild(style); - - function updateCSSRule(identifier, value) { - if (styleRuleMap[identifier] === undefined) { - return; - } - - var rule = styleRuleMap[identifier].replace(/VALUE/g, value + ' !important'); - if (!rule) { - return; - } - - var rules = []; - if (rule.indexOf('__COMBO_RULE__')) { - rules = rule.split('__COMBO_RULE__'); - } - else { - rules = [rule]; - } - - for (var i = 0, length = rules.length; i < length; i++) { - try { - style.sheet.insertRule(rules[i], style.sheet.cssRules.length); - } - catch (e) { - // ignore errors for unknown placeholder selectors - if (!/[a-z]+\-placeholder/.test(rules[i])) { - console.debug(e.message); - } - } - } - } - - var elements = elByClass('styleVariableColor', elById('spVariablesWrapper')); - [].forEach.call(elements, function(colorField) { - var variableName = elData(colorField, 'store').replace(/_value$/, ''); - - var observer = new MutationObserver(function(mutations) { - mutations.forEach(function(mutation) { - if (mutation.attributeName === 'style') { - updateCSSRule(variableName, colorField.style.getPropertyValue('background-color')); - } - }); - }); - - observer.observe(colorField, { - attributes: true - }); - - updateCSSRule(variableName, colorField.style.getPropertyValue('background-color')); - }); - - // category selection by clicking on the area - var buttonToggleColorPalette = elBySel('.jsButtonToggleColorPalette'); - var buttonSelectCategoryByClick = elBySel('.jsButtonSelectCategoryByClick'); - var toggleSelectionMode = function() { - buttonSelectCategoryByClick.classList.toggle('active'); - buttonToggleColorPalette.classList.toggle('disabled'); - _stylePreviewWindow.classList.toggle('spShowRegions'); - _stylePreviewRegionMarker.classList.toggle('forceHide'); - select.disabled = !select.disabled; - }; - - buttonSelectCategoryByClick.addEventListener('click', function (event) { - event.preventDefault(); - - toggleSelectionMode(); - }); - - elBySelAll('[data-region]', _stylePreviewWindow, function (region) { - region.addEventListener('click', function (event) { - if (!_stylePreviewWindow.classList.contains('spShowRegions')) { - return; - } - - event.preventDefault(); - event.stopPropagation(); - - toggleSelectionMode(); - - select.value = elData(region, 'region'); - - // Programmatically trigger the change event handler, rather than dispatching an event, - // because Firefox fails to execute the event if it has previously been disabled. - // See https://bugzilla.mozilla.org/show_bug.cgi?id=1426856 - callbackChange(); - }); - }); - - // toggle view - var spSelectCategory = elById('spSelectCategory'); - buttonToggleColorPalette.addEventListener('click', function (event) { - event.preventDefault(); - - buttonSelectCategoryByClick.classList.toggle('disabled'); - elToggle(spSelectCategory); - buttonToggleColorPalette.classList.toggle('active'); - _stylePreviewWindow.classList.toggle('spColorPalette'); - _stylePreviewRegionMarker.classList.toggle('forceHide'); - select.disabled = !select.disabled; - }); - }, - - hideVisualEditor: function() { - elHide(_stylePreviewWindow); - elById('spVariablesWrapper').style.removeProperty('transform'); - elHide(elById('stylePreviewRegionMarker')); - - _isSmartphone = true; - }, - - showVisualEditor: function() { - elShow(_stylePreviewWindow); - - window.setTimeout(function() { - Core.triggerEvent(elById('spCategories'), 'change'); - }, 100); - - _isSmartphone = false; - } - }; -}); diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Acp/Ui/Style/Editor.ts b/wcfsetup/install/files/ts/WoltLabSuite/Core/Acp/Ui/Style/Editor.ts new file mode 100644 index 0000000000..507d713e35 --- /dev/null +++ b/wcfsetup/install/files/ts/WoltLabSuite/Core/Acp/Ui/Style/Editor.ts @@ -0,0 +1,346 @@ +/** + * Provides the style editor. + * + * @author Alexander Ebert + * @copyright 2001-2019 WoltLab GmbH + * @license GNU Lesser General Public License + * @module WoltLabSuite/Core/Acp/Ui/Style/Editor + */ + +import * as Ajax from "../../../Ajax"; +import * as Core from "../../../Core"; +import DomUtil from "../../../Dom/Util"; +import * as EventHandler from "../../../Event/Handler"; +import * as UiScreen from "../../../Ui/Screen"; + +const _stylePreviewRegions = new Map(); +let _stylePreviewRegionMarker: HTMLElement; +const _stylePreviewWindow = document.getElementById("spWindow")!; + +let _isVisible = true; +let _isSmartphone = false; +let _updateRegionMarker: () => void; + +interface StyleRuleMap { + [key: string]: string; +} + +interface StyleEditorOptions { + isTainted: boolean; + styleId: number; + styleRuleMap: StyleRuleMap; +} + +/** + * Handles the switch between static and fluid layout. + */ +function handleLayoutWidth(): void { + const useFluidLayout = document.getElementById("useFluidLayout") as HTMLInputElement; + const fluidLayoutMinWidth = document.getElementById("fluidLayoutMinWidth") as HTMLInputElement; + const fluidLayoutMaxWidth = document.getElementById("fluidLayoutMaxWidth") as HTMLInputElement; + const fixedLayoutVariables = document.getElementById("fixedLayoutVariables") as HTMLDListElement; + + function change(): void { + if (useFluidLayout.checked) { + DomUtil.show(fluidLayoutMinWidth); + DomUtil.show(fluidLayoutMaxWidth); + DomUtil.hide(fixedLayoutVariables); + } else { + DomUtil.hide(fluidLayoutMinWidth); + DomUtil.hide(fluidLayoutMaxWidth); + DomUtil.show(fixedLayoutVariables); + } + } + + useFluidLayout.addEventListener("change", change); + + change(); +} + +/** + * Handles SCSS input fields. + */ +function handleScss(isTainted: boolean): void { + const individualScss = document.getElementById("individualScss")!; + const overrideScss = document.getElementById("overrideScss")!; + + if (isTainted) { + EventHandler.add("com.woltlab.wcf.simpleTabMenu_styleTabMenuContainer", "select", () => { + (individualScss as any).codemirror.refresh(); + (overrideScss as any).codemirror.refresh(); + }); + } else { + EventHandler.add("com.woltlab.wcf.simpleTabMenu_advanced", "select", (data: { activeName: string }) => { + if (data.activeName === "advanced-custom") { + (document.getElementById("individualScssCustom") as any).codemirror.refresh(); + (document.getElementById("overrideScssCustom") as any).codemirror.refresh(); + } else if (data.activeName === "advanced-original") { + (individualScss as any).codemirror.refresh(); + (overrideScss as any).codemirror.refresh(); + } + }); + } +} + +function handleProtection(styleId: number): void { + const button = document.getElementById("styleDisableProtectionSubmit") as HTMLButtonElement; + const checkbox = document.getElementById("styleDisableProtectionConfirm") as HTMLInputElement; + + checkbox.addEventListener("change", () => { + button.disabled = !checkbox.checked; + }); + + button.addEventListener("click", () => { + Ajax.apiOnce({ + data: { + actionName: "markAsTainted", + className: "wcf\\data\\style\\StyleAction", + objectIDs: [styleId], + }, + success: () => { + window.location.reload(); + }, + }); + }); +} + +function initVisualEditor(styleRuleMap: StyleRuleMap): void { + _stylePreviewWindow.querySelectorAll("[data-region]").forEach((region: HTMLElement) => { + _stylePreviewRegions.set(region.dataset.region!, region); + }); + + _stylePreviewRegionMarker = document.createElement("div"); + _stylePreviewRegionMarker.id = "stylePreviewRegionMarker"; + _stylePreviewRegionMarker.innerHTML = '
'; + DomUtil.hide(_stylePreviewRegionMarker); + document.getElementById("colors")!.appendChild(_stylePreviewRegionMarker); + + const container = document.getElementById("spSidebar")!; + const select = document.getElementById("spCategories") as HTMLSelectElement; + let lastValue = select.value; + + _updateRegionMarker = (): void => { + if (_isSmartphone) { + return; + } + + if (lastValue === "none") { + DomUtil.hide(_stylePreviewRegionMarker); + return; + } + + const region = _stylePreviewRegions.get(lastValue)!; + const rect = region.getBoundingClientRect(); + + let top = rect.top + (window.scrollY || window.pageYOffset); + + DomUtil.setStyles(_stylePreviewRegionMarker, { + height: `${region.clientHeight + 20}px`, + left: `${rect.left + document.body.scrollLeft - 10}px`, + top: `${top - 10}px`, + width: `${region.clientWidth + 20}px`, + }); + + DomUtil.show(_stylePreviewRegionMarker); + + top = DomUtil.offset(region).top; + // `+ 80` = account for sticky header + selection markers (20px) + const firstVisiblePixel = (window.pageYOffset || window.scrollY) + 80; + if (firstVisiblePixel > top) { + window.scrollTo(0, Math.max(top - 80, 0)); + } else { + const lastVisiblePixel = window.innerHeight + (window.pageYOffset || window.scrollY); + if (lastVisiblePixel < top) { + window.scrollTo(0, top); + } else { + const bottom = top + region.offsetHeight + 20; + if (lastVisiblePixel < bottom) { + window.scrollBy(0, bottom - top); + } + } + } + }; + + const apiVersions = container.querySelector('.spSidebarBox[data-category="apiVersion"]') as HTMLElement; + const callbackChange = () => { + let element = container.querySelector(`.spSidebarBox[data-category="${lastValue}"]`) as HTMLElement; + DomUtil.hide(element); + + lastValue = select.value; + element = container.querySelector(`.spSidebarBox[data-category="${lastValue}"]`) as HTMLElement; + DomUtil.show(element); + + const showCompatibilityNotice = element.querySelector(".spApiVersion") !== null; + if (showCompatibilityNotice) { + DomUtil.show(apiVersions); + } else { + DomUtil.hide(apiVersions); + } + + // set region marker + _updateRegionMarker(); + }; + select.addEventListener("change", callbackChange); + + // apply CSS rules + const style = document.createElement("style"); + style.appendChild(document.createTextNode("")); + style.dataset.createdBy = "WoltLab/Acp/Ui/Style/Editor"; + document.head.appendChild(style); + + function updateCSSRule(identifier: string, value: string): void { + if (styleRuleMap[identifier] === undefined) { + return; + } + + const rule = styleRuleMap[identifier].replace(/VALUE/g, value + " !important"); + if (!rule) { + return; + } + + let rules: string[]; + if (rule.indexOf("__COMBO_RULE__")) { + rules = rule.split("__COMBO_RULE__"); + } else { + rules = [rule]; + } + + rules.forEach((rule) => { + try { + style.sheet!.insertRule(rule, style.sheet!.cssRules.length); + } catch (e) { + // ignore errors for unknown placeholder selectors + if (!/[a-z]+-placeholder/.test(rule)) { + console.debug(e.message); + } + } + }); + } + + const wrapper = document.getElementById("spVariablesWrapper")!; + wrapper.querySelectorAll(".styleVariableColor").forEach((colorField: HTMLElement) => { + const variableName = colorField.dataset.store!.replace(/_value$/, ""); + + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.attributeName === "style") { + updateCSSRule(variableName, colorField.style.getPropertyValue("background-color")); + } + }); + }); + + observer.observe(colorField, { + attributes: true, + }); + + updateCSSRule(variableName, colorField.style.getPropertyValue("background-color")); + }); + + // category selection by clicking on the area + const buttonToggleColorPalette = document.querySelector(".jsButtonToggleColorPalette") as HTMLAnchorElement; + const buttonSelectCategoryByClick = document.querySelector(".jsButtonSelectCategoryByClick") as HTMLAnchorElement; + + function toggleSelectionMode(): void { + buttonSelectCategoryByClick.classList.toggle("active"); + buttonToggleColorPalette.classList.toggle("disabled"); + _stylePreviewWindow.classList.toggle("spShowRegions"); + _stylePreviewRegionMarker.classList.toggle("forceHide"); + select.disabled = !select.disabled; + } + + buttonSelectCategoryByClick.addEventListener("click", (event) => { + event.preventDefault(); + + toggleSelectionMode(); + }); + + _stylePreviewWindow.querySelectorAll("[data-region]").forEach((region: HTMLElement) => { + region.addEventListener("click", (event) => { + if (!_stylePreviewWindow.classList.contains("spShowRegions")) { + return; + } + + event.preventDefault(); + event.stopPropagation(); + + toggleSelectionMode(); + + select.value = region.dataset.region!; + + // Programmatically trigger the change event handler, rather than dispatching an event, + // because Firefox fails to execute the event if it has previously been disabled. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=1426856 + callbackChange(); + }); + }); + + // toggle view + const spSelectCategory = document.getElementById("spSelectCategory") as HTMLSelectElement; + buttonToggleColorPalette.addEventListener("click", (event) => { + event.preventDefault(); + + buttonSelectCategoryByClick.classList.toggle("disabled"); + DomUtil.toggle(spSelectCategory); + buttonToggleColorPalette.classList.toggle("active"); + _stylePreviewWindow.classList.toggle("spColorPalette"); + _stylePreviewRegionMarker.classList.toggle("forceHide"); + select.disabled = !select.disabled; + }); +} + +/** + * Sets up dynamic style options. + */ +export function setup(options: StyleEditorOptions): void { + handleLayoutWidth(); + handleScss(options.isTainted); + + if (!options.isTainted) { + handleProtection(options.styleId); + } + + initVisualEditor(options.styleRuleMap); + + UiScreen.on("screen-sm-down", { + match() { + hideVisualEditor(); + }, + unmatch() { + showVisualEditor(); + }, + setup() { + hideVisualEditor(); + }, + }); + + function callbackRegionMarker(): void { + if (_isVisible) { + _updateRegionMarker(); + } + } + + window.addEventListener("resize", callbackRegionMarker); + EventHandler.add("com.woltlab.wcf.AcpMenu", "resize", callbackRegionMarker); + EventHandler.add("com.woltlab.wcf.simpleTabMenu_styleTabMenuContainer", "select", function (data) { + _isVisible = data.activeName === "colors"; + callbackRegionMarker(); + }); +} + +export function hideVisualEditor(): void { + DomUtil.hide(_stylePreviewWindow); + document.getElementById("spVariablesWrapper")!.style.removeProperty("transform"); + DomUtil.hide(document.getElementById("stylePreviewRegionMarker")!); + + _isSmartphone = true; +} + +export function showVisualEditor(): void { + DomUtil.show(_stylePreviewWindow); + + window.setTimeout(() => { + Core.triggerEvent(document.getElementById("spCategories")!, "change"); + }, 100); + + _isSmartphone = false; +} diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Dom/Util.ts b/wcfsetup/install/files/ts/WoltLabSuite/Core/Dom/Util.ts index f7b61452ab..4fad7f9bbe 100644 --- a/wcfsetup/install/files/ts/WoltLabSuite/Core/Dom/Util.ts +++ b/wcfsetup/install/files/ts/WoltLabSuite/Core/Dom/Util.ts @@ -420,6 +420,17 @@ const DomUtil = { return element.style.getPropertyValue("display") === "none"; }, + /** + * Shorthand function to toggle the element visibility using either `hide()` or `show()`. + */ + toggle(element: HTMLElement): void { + if (this.isHidden(element)) { + this.show(element); + } else { + this.hide(element); + } + }, + /** * Displays or removes an error message below the provided element. */