| 1 | /** |
| 2 | * Provides the style editor. |
| 3 | * |
| 4 | * @author Alexander Ebert |
| 5 | * @copyright 2001-2019 WoltLab GmbH |
| 6 | * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> |
| 7 | */ |
| 8 | define(["require", "exports", "tslib", "../../../Ajax", "../../../Core", "../../../Dom/Util", "../../../Event/Handler", "../../../Ui/Screen", "./DarkMode"], function (require, exports, tslib_1, Ajax, Core, Util_1, EventHandler, UiScreen, DarkMode_1) { |
| 9 | "use strict"; |
| 10 | Object.defineProperty(exports, "__esModule", { value: true }); |
| 11 | exports.setup = setup; |
| 12 | exports.hideVisualEditor = hideVisualEditor; |
| 13 | exports.showVisualEditor = showVisualEditor; |
| 14 | Ajax = tslib_1.__importStar(Ajax); |
| 15 | Core = tslib_1.__importStar(Core); |
| 16 | Util_1 = tslib_1.__importDefault(Util_1); |
| 17 | EventHandler = tslib_1.__importStar(EventHandler); |
| 18 | UiScreen = tslib_1.__importStar(UiScreen); |
| 19 | const _stylePreviewRegions = new Map(); |
| 20 | let _stylePreviewRegionMarker; |
| 21 | const _stylePreviewWindow = document.getElementById("spWindow"); |
| 22 | let _isVisible = true; |
| 23 | let _isSmartphone = false; |
| 24 | let _updateRegionMarker; |
| 25 | /** |
| 26 | * Handles the switch between static and fluid layout. |
| 27 | */ |
| 28 | function handleLayoutWidth() { |
| 29 | const useFluidLayout = document.getElementById("useFluidLayout"); |
| 30 | const fluidLayoutMinWidth = document.getElementById("fluidLayoutMinWidth"); |
| 31 | const fluidLayoutMaxWidth = document.getElementById("fluidLayoutMaxWidth"); |
| 32 | const fixedLayoutVariables = document.getElementById("fixedLayoutVariables"); |
| 33 | function change() { |
| 34 | if (useFluidLayout.checked) { |
| 35 | Util_1.default.show(fluidLayoutMinWidth); |
| 36 | Util_1.default.show(fluidLayoutMaxWidth); |
| 37 | Util_1.default.hide(fixedLayoutVariables); |
| 38 | } |
| 39 | else { |
| 40 | Util_1.default.hide(fluidLayoutMinWidth); |
| 41 | Util_1.default.hide(fluidLayoutMaxWidth); |
| 42 | Util_1.default.show(fixedLayoutVariables); |
| 43 | } |
| 44 | } |
| 45 | useFluidLayout.addEventListener("change", change); |
| 46 | change(); |
| 47 | } |
| 48 | /** |
| 49 | * Handles SCSS input fields. |
| 50 | */ |
| 51 | function handleScss(isTainted) { |
| 52 | const individualScss = document.getElementById("individualScss"); |
| 53 | const overrideScss = document.getElementById("overrideScss"); |
| 54 | const refreshCodeMirror = (element) => { |
| 55 | element.codemirror.refresh(); |
| 56 | element.codemirror.setCursor(element.codemirror.getCursor()); |
| 57 | }; |
| 58 | if (isTainted) { |
| 59 | EventHandler.add("com.woltlab.wcf.simpleTabMenu_styleTabMenuContainer", "select", () => { |
| 60 | refreshCodeMirror(individualScss); |
| 61 | refreshCodeMirror(overrideScss); |
| 62 | }); |
| 63 | } |
| 64 | else { |
| 65 | EventHandler.add("com.woltlab.wcf.simpleTabMenu_advanced", "select", (data) => { |
| 66 | if (data.activeName === "advanced-custom") { |
| 67 | refreshCodeMirror(document.getElementById("individualScssCustom")); |
| 68 | refreshCodeMirror(document.getElementById("overrideScssCustom")); |
| 69 | } |
| 70 | else if (data.activeName === "advanced-original") { |
| 71 | refreshCodeMirror(individualScss); |
| 72 | refreshCodeMirror(overrideScss); |
| 73 | } |
| 74 | }); |
| 75 | } |
| 76 | } |
| 77 | function handleProtection(styleId) { |
| 78 | const button = document.getElementById("styleDisableProtectionSubmit"); |
| 79 | const checkbox = document.getElementById("styleDisableProtectionConfirm"); |
| 80 | checkbox.addEventListener("change", () => { |
| 81 | button.disabled = !checkbox.checked; |
| 82 | }); |
| 83 | button.addEventListener("click", () => { |
| 84 | Ajax.apiOnce({ |
| 85 | data: { |
| 86 | actionName: "markAsTainted", |
| 87 | className: "wcf\\data\\style\\StyleAction", |
| 88 | objectIDs: [styleId], |
| 89 | }, |
| 90 | success: () => { |
| 91 | window.location.reload(); |
| 92 | }, |
| 93 | }); |
| 94 | }); |
| 95 | } |
| 96 | function initVisualEditor() { |
| 97 | _stylePreviewWindow.querySelectorAll("[data-region]").forEach((region) => { |
| 98 | _stylePreviewRegions.set(region.dataset.region, region); |
| 99 | }); |
| 100 | _stylePreviewRegionMarker = document.createElement("div"); |
| 101 | _stylePreviewRegionMarker.id = "stylePreviewRegionMarker"; |
| 102 | _stylePreviewRegionMarker.innerHTML = '<div id="stylePreviewRegionMarkerBottom"></div>'; |
| 103 | Util_1.default.hide(_stylePreviewRegionMarker); |
| 104 | document.getElementById("colors").appendChild(_stylePreviewRegionMarker); |
| 105 | const container = document.getElementById("spSidebar"); |
| 106 | const select = document.getElementById("spCategories"); |
| 107 | let lastValue = select.value; |
| 108 | _updateRegionMarker = () => { |
| 109 | if (_isSmartphone) { |
| 110 | return; |
| 111 | } |
| 112 | if (lastValue === "none") { |
| 113 | Util_1.default.hide(_stylePreviewRegionMarker); |
| 114 | return; |
| 115 | } |
| 116 | const region = _stylePreviewRegions.get(lastValue); |
| 117 | const rect = region.getBoundingClientRect(); |
| 118 | let top = rect.top + (window.scrollY || window.pageYOffset); |
| 119 | Util_1.default.setStyles(_stylePreviewRegionMarker, { |
| 120 | height: `${region.clientHeight + 20}px`, |
| 121 | left: `${rect.left + document.body.scrollLeft - 10}px`, |
| 122 | top: `${top - 10}px`, |
| 123 | width: `${region.clientWidth + 20}px`, |
| 124 | }); |
| 125 | Util_1.default.show(_stylePreviewRegionMarker); |
| 126 | top = Util_1.default.offset(region).top; |
| 127 | // `+ 80` = account for sticky header + selection markers (20px) |
| 128 | const firstVisiblePixel = (window.pageYOffset || window.scrollY) + 80; |
| 129 | if (firstVisiblePixel > top) { |
| 130 | window.scrollTo(0, Math.max(top - 80, 0)); |
| 131 | } |
| 132 | else { |
| 133 | const lastVisiblePixel = window.innerHeight + (window.pageYOffset || window.scrollY); |
| 134 | if (lastVisiblePixel < top) { |
| 135 | window.scrollTo(0, top); |
| 136 | } |
| 137 | else { |
| 138 | const bottom = top + region.offsetHeight + 20; |
| 139 | if (lastVisiblePixel < bottom) { |
| 140 | window.scrollBy(0, bottom - top); |
| 141 | } |
| 142 | } |
| 143 | } |
| 144 | }; |
| 145 | const callbackChange = () => { |
| 146 | let element = container.querySelector(`.spSidebarBox[data-category="${lastValue}"]`); |
| 147 | Util_1.default.hide(element); |
| 148 | lastValue = select.value; |
| 149 | element = container.querySelector(`.spSidebarBox[data-category="${lastValue}"]`); |
| 150 | Util_1.default.show(element); |
| 151 | // set region marker |
| 152 | _updateRegionMarker(); |
| 153 | }; |
| 154 | select.addEventListener("change", callbackChange); |
| 155 | // apply CSS rules |
| 156 | const style = document.createElement("style"); |
| 157 | style.appendChild(document.createTextNode("")); |
| 158 | style.dataset.createdBy = "WoltLab/Acp/Ui/Style/Editor"; |
| 159 | document.head.appendChild(style); |
| 160 | const spWindow = document.getElementById("spWindow"); |
| 161 | const wrapper = document.getElementById("spVariablesWrapper"); |
| 162 | wrapper.querySelectorAll(".styleVariableColor").forEach((colorField) => { |
| 163 | const variableName = colorField.dataset.store.replace(/_value$/, ""); |
| 164 | const observer = new MutationObserver((mutations) => { |
| 165 | mutations.forEach((mutation) => { |
| 166 | if (mutation.attributeName === "style") { |
| 167 | spWindow.style.setProperty(`--${variableName}`, colorField.style.getPropertyValue("background-color")); |
| 168 | } |
| 169 | }); |
| 170 | }); |
| 171 | observer.observe(colorField, { |
| 172 | attributes: true, |
| 173 | }); |
| 174 | spWindow.style.setProperty(`--${variableName}`, colorField.style.getPropertyValue("background-color")); |
| 175 | }); |
| 176 | // category selection by clicking on the area |
| 177 | const buttonToggleColorPalette = document.querySelector(".jsButtonToggleColorPalette"); |
| 178 | const buttonSelectCategoryByClick = document.querySelector(".jsButtonSelectCategoryByClick"); |
| 179 | function toggleSelectionMode() { |
| 180 | buttonSelectCategoryByClick.classList.toggle("active"); |
| 181 | buttonToggleColorPalette.classList.toggle("disabled"); |
| 182 | _stylePreviewWindow.classList.toggle("spShowRegions"); |
| 183 | _stylePreviewRegionMarker.classList.toggle("forceHide"); |
| 184 | select.disabled = !select.disabled; |
| 185 | } |
| 186 | buttonSelectCategoryByClick.addEventListener("click", (event) => { |
| 187 | event.preventDefault(); |
| 188 | toggleSelectionMode(); |
| 189 | }); |
| 190 | _stylePreviewWindow.querySelectorAll("[data-region]").forEach((region) => { |
| 191 | region.addEventListener("click", (event) => { |
| 192 | if (!_stylePreviewWindow.classList.contains("spShowRegions")) { |
| 193 | return; |
| 194 | } |
| 195 | event.preventDefault(); |
| 196 | event.stopPropagation(); |
| 197 | toggleSelectionMode(); |
| 198 | select.value = region.dataset.region; |
| 199 | // Programmatically trigger the change event handler, rather than dispatching an event, |
| 200 | // because Firefox fails to execute the event if it has previously been disabled. |
| 201 | // See https://bugzilla.mozilla.org/show_bug.cgi?id=1426856 |
| 202 | callbackChange(); |
| 203 | }); |
| 204 | }); |
| 205 | // toggle view |
| 206 | const spSelectCategory = document.getElementById("spSelectCategory"); |
| 207 | buttonToggleColorPalette.addEventListener("click", (event) => { |
| 208 | event.preventDefault(); |
| 209 | buttonSelectCategoryByClick.classList.toggle("disabled"); |
| 210 | Util_1.default.toggle(spSelectCategory); |
| 211 | buttonToggleColorPalette.classList.toggle("active"); |
| 212 | _stylePreviewWindow.classList.toggle("spColorPalette"); |
| 213 | _stylePreviewRegionMarker.classList.toggle("forceHide"); |
| 214 | select.disabled = !select.disabled; |
| 215 | }); |
| 216 | } |
| 217 | /** |
| 218 | * Sets up dynamic style options. |
| 219 | */ |
| 220 | function setup(options) { |
| 221 | handleLayoutWidth(); |
| 222 | handleScss(options.isTainted); |
| 223 | (0, DarkMode_1.setup)(); |
| 224 | if (!options.isTainted) { |
| 225 | handleProtection(options.styleId); |
| 226 | } |
| 227 | initVisualEditor(); |
| 228 | UiScreen.on("screen-sm-down", { |
| 229 | match() { |
| 230 | hideVisualEditor(); |
| 231 | }, |
| 232 | unmatch() { |
| 233 | showVisualEditor(); |
| 234 | }, |
| 235 | setup() { |
| 236 | hideVisualEditor(); |
| 237 | }, |
| 238 | }); |
| 239 | function callbackRegionMarker() { |
| 240 | if (_isVisible) { |
| 241 | _updateRegionMarker(); |
| 242 | } |
| 243 | } |
| 244 | window.addEventListener("resize", callbackRegionMarker); |
| 245 | EventHandler.add("com.woltlab.wcf.AcpMenu", "resize", callbackRegionMarker); |
| 246 | EventHandler.add("com.woltlab.wcf.simpleTabMenu_styleTabMenuContainer", "select", function (data) { |
| 247 | _isVisible = data.activeName === "colors"; |
| 248 | callbackRegionMarker(); |
| 249 | }); |
| 250 | } |
| 251 | function hideVisualEditor() { |
| 252 | Util_1.default.hide(_stylePreviewWindow); |
| 253 | document.getElementById("spVariablesWrapper").style.removeProperty("transform"); |
| 254 | Util_1.default.hide(document.getElementById("stylePreviewRegionMarker")); |
| 255 | _isSmartphone = true; |
| 256 | } |
| 257 | function showVisualEditor() { |
| 258 | Util_1.default.show(_stylePreviewWindow); |
| 259 | window.setTimeout(() => { |
| 260 | Core.triggerEvent(document.getElementById("spCategories"), "change"); |
| 261 | }, 100); |
| 262 | _isSmartphone = false; |
| 263 | } |
| 264 | }); |